/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Haystack Software Inc. All rights reserved.
 *  Licensed under the PolyForm Strict License 1.0.0. See License.txt in the project root for
 *  license information.
 *--------------------------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See code-license.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { Action } from "vs/base/common/actions"
import { IStringDictionary } from "vs/base/common/collections"
import { Emitter, Event } from "vs/base/common/event"
import * as glob from "vs/base/common/glob"
import * as json from "vs/base/common/json"
import {
  Disposable,
  dispose,
  IDisposable,
  IReference,
} from "vs/base/common/lifecycle"
import { LRUCache, Touch } from "vs/base/common/map"
import * as Objects from "vs/base/common/objects"
import { ValidationState, ValidationStatus } from "vs/base/common/parsers"
import * as Platform from "vs/base/common/platform"
import { TerminateResponseCode } from "vs/base/common/processes"
import * as resources from "vs/base/common/resources"
import Severity from "vs/base/common/severity"
import * as Types from "vs/base/common/types"
import { URI } from "vs/base/common/uri"
import * as UUID from "vs/base/common/uuid"
import * as nls from "vs/nls"
import {
  CommandsRegistry,
  ICommandService,
} from "vs/platform/commands/common/commands"
import {
  ConfigurationTarget,
  IConfigurationService,
} from "vs/platform/configuration/common/configuration"
import {
  IFileService,
  IFileStatWithPartialMetadata,
} from "vs/platform/files/common/files"
import { IMarkerService } from "vs/platform/markers/common/markers"
import {
  IProgressOptions,
  IProgressService,
  ProgressLocation,
} from "vs/platform/progress/common/progress"
import {
  IStorageService,
  StorageScope,
  StorageTarget,
} from "vs/platform/storage/common/storage"
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry"
import {
  INamedProblemMatcher,
  ProblemMatcherRegistry,
} from "vs/workbench/contrib/tasks/common/problemMatcher"
import { IExtensionService } from "vs/workbench/services/extensions/common/extensions"

import { IDialogService } from "vs/platform/dialogs/common/dialogs"
import { INotificationService } from "vs/platform/notification/common/notification"
import { IOpenerService } from "vs/platform/opener/common/opener"

import { IModelService } from "vs/editor/common/services/model"

import {
  IWorkspace,
  IWorkspaceContextService,
  IWorkspaceFolder,
  WorkbenchState,
  WorkspaceFolder,
} from "vs/platform/workspace/common/workspace"
import { Markers } from "vs/workbench/contrib/markers/common/markers"
import { IConfigurationResolverService } from "vs/workbench/services/configurationResolver/common/configurationResolver"
import { IEditorService } from "vs/workbench/services/editor/common/editorService"

import {
  IOutputChannel,
  IOutputService,
} from "vs/workbench/services/output/common/output"
import { ITextFileService } from "vs/workbench/services/textfile/common/textfiles"

import {
  ITerminalGroupService,
  ITerminalService,
} from "vs/workbench/contrib/terminal/browser/terminal"
import { ITerminalProfileResolverService } from "vs/workbench/contrib/terminal/common/terminal"

import {
  ConfiguringTask,
  ContributedTask,
  CustomTask,
  ExecutionEngine,
  InMemoryTask,
  ITaskEvent,
  ITaskIdentifier,
  ITaskSet,
  JsonSchemaVersion,
  KeyedTaskIdentifier,
  RuntimeType,
  Task,
  TASK_RUNNING_STATE,
  TaskDefinition,
  TaskEventKind,
  TaskGroup,
  TaskRunSource,
  TaskSettingId,
  TaskSorter,
  TaskSourceKind,
  TasksSchemaProperties,
  USER_TASKS_GROUP_KEY,
} from "vs/workbench/contrib/tasks/common/tasks"
import {
  CustomExecutionSupportedContext,
  ICustomizationProperties,
  IProblemMatcherRunOptions,
  ITaskFilter,
  ITaskProvider,
  ITaskService,
  IWorkspaceFolderTaskResult,
  ProcessExecutionSupportedContext,
  ServerlessWebContext,
  ShellExecutionSupportedContext,
  TaskCommandsRegistered,
  TaskExecutionSupportedContext,
} from "vs/workbench/contrib/tasks/common/taskService"
import {
  ITaskExecuteResult,
  ITaskResolver,
  ITaskSummary,
  ITaskSystem,
  ITaskSystemInfo,
  ITaskTerminateResponse,
  TaskError,
  TaskErrors,
  TaskExecuteKind,
} from "vs/workbench/contrib/tasks/common/taskSystem"
import { getTemplates as getTaskTemplates } from "vs/workbench/contrib/tasks/common/taskTemplates"

import * as TaskConfig from "../common/taskConfiguration"
import { TerminalTaskSystem } from "./terminalTaskSystem"

import {
  IQuickInputService,
  IQuickPick,
  IQuickPickItem,
  IQuickPickSeparator,
  QuickPickInput,
} from "vs/platform/quickinput/common/quickInput"

import {
  IContextKey,
  IContextKeyService,
} from "vs/platform/contextkey/common/contextkey"
import { TaskDefinitionRegistry } from "vs/workbench/contrib/tasks/common/taskDefinitionRegistry"

import { raceTimeout } from "vs/base/common/async"
import {
  CancellationToken,
  CancellationTokenSource,
} from "vs/base/common/cancellation"
import { toFormattedString } from "vs/base/common/jsonFormatter"
import { Schemas } from "vs/base/common/network"
import { ThemeIcon } from "vs/base/common/themables"
import {
  IResolvedTextEditorModel,
  ITextModelService,
} from "vs/editor/common/services/resolverService"
import { TextEditorSelectionRevealType } from "vs/platform/editor/common/editor"
import { IInstantiationService } from "vs/platform/instantiation/common/instantiation"
import { ILogService } from "vs/platform/log/common/log"
import { TerminalExitReason } from "vs/platform/terminal/common/terminal"
import { IThemeService } from "vs/platform/theme/common/themeService"
import {
  IWorkspaceTrustManagementService,
  IWorkspaceTrustRequestService,
} from "vs/platform/workspace/common/workspaceTrust"
import { VirtualWorkspaceContext } from "vs/workbench/common/contextkeys"
import { EditorResourceAccessor, SaveReason } from "vs/workbench/common/editor"
import { IViewDescriptorService } from "vs/workbench/common/views"
import { IViewsService } from "vs/workbench/services/views/common/viewsService"
import {
  configureTaskIcon,
  isWorkspaceFolder,
  ITaskQuickPickEntry,
  QUICKOPEN_DETAIL_CONFIG,
  QUICKOPEN_SKIP_CONFIG,
  TaskQuickPick,
} from "vs/workbench/contrib/tasks/browser/taskQuickPick"
import { IWorkbenchEnvironmentService } from "vs/workbench/services/environment/common/environmentService"
import {
  ILifecycleService,
  ShutdownReason,
  StartupKind,
} from "vs/workbench/services/lifecycle/common/lifecycle"
import { IPaneCompositePartService } from "vs/workbench/services/panecomposite/browser/panecomposite"
import { IPathService } from "vs/workbench/services/path/common/pathService"
import { IPreferencesService } from "vs/workbench/services/preferences/common/preferences"
import { IRemoteAgentService } from "vs/workbench/services/remote/common/remoteAgentService"

const QUICKOPEN_HISTORY_LIMIT_CONFIG = "task.quickOpen.history"
const PROBLEM_MATCHER_NEVER_CONFIG = "task.problemMatchers.neverPrompt"
const USE_SLOW_PICKER = "task.quickOpen.showAll"

export namespace ConfigureTaskAction {
  export const ID = "workbench.action.tasks.configureTaskRunner"
  export const TEXT = nls.localize2(
    "ConfigureTaskRunnerAction.label",
    "Configure Task",
  )
}

export type TaskQuickPickEntryType =
  | (IQuickPickItem & { task: Task })
  | (IQuickPickItem & { folder: IWorkspaceFolder })
  | (IQuickPickItem & { settingType: string })

class ProblemReporter implements TaskConfig.IProblemReporter {
  private _validationStatus: ValidationStatus

  constructor(private _outputChannel: IOutputChannel) {
    this._validationStatus = new ValidationStatus()
  }

  public info(message: string): void {
    this._validationStatus.state = ValidationState.Info
    this._outputChannel.append(message + "\n")
  }

  public warn(message: string): void {
    this._validationStatus.state = ValidationState.Warning
    this._outputChannel.append(message + "\n")
  }

  public error(message: string): void {
    this._validationStatus.state = ValidationState.Error
    this._outputChannel.append(message + "\n")
  }

  public fatal(message: string): void {
    this._validationStatus.state = ValidationState.Fatal
    this._outputChannel.append(message + "\n")
  }

  public get status(): ValidationStatus {
    return this._validationStatus
  }
}

export interface IWorkspaceFolderConfigurationResult {
  workspaceFolder: IWorkspaceFolder
  config: TaskConfig.IExternalTaskRunnerConfiguration | undefined
  hasErrors: boolean
}

interface ICommandUpgrade {
  command?: string
  args?: string[]
}

class TaskMap {
  private _store: Map<string, Task[]> = new Map()

  public forEach(callback: (value: Task[], folder: string) => void): void {
    this._store.forEach(callback)
  }

  public static getKey(
    workspaceFolder: IWorkspace | IWorkspaceFolder | string,
  ): string {
    let key: string | undefined
    if (Types.isString(workspaceFolder)) {
      key = workspaceFolder
    } else {
      const uri: URI | null | undefined = isWorkspaceFolder(workspaceFolder)
        ? workspaceFolder.uri
        : workspaceFolder.configuration
      key = uri ? uri.toString() : ""
    }
    return key
  }

  public get(workspaceFolder: IWorkspace | IWorkspaceFolder | string): Task[] {
    const key = TaskMap.getKey(workspaceFolder)
    let result: Task[] | undefined = this._store.get(key)
    if (!result) {
      result = []
      this._store.set(key, result)
    }
    return result
  }

  public add(
    workspaceFolder: IWorkspace | IWorkspaceFolder | string,
    ...task: Task[]
  ): void {
    const key = TaskMap.getKey(workspaceFolder)
    let values = this._store.get(key)
    if (!values) {
      values = []
      this._store.set(key, values)
    }
    values.push(...task)
  }

  public all(): Task[] {
    const result: Task[] = []
    this._store.forEach((values) => result.push(...values))
    return result
  }
}

export abstract class AbstractTaskService
  extends Disposable
  implements ITaskService
{
  // private static autoDetectTelemetryName: string = 'taskServer.autoDetect';
  private static readonly RecentlyUsedTasks_Key =
    "workbench.tasks.recentlyUsedTasks"
  private static readonly RecentlyUsedTasks_KeyV2 =
    "workbench.tasks.recentlyUsedTasks2"
  private static readonly PersistentTasks_Key =
    "workbench.tasks.persistentTasks"
  private static readonly IgnoreTask010DonotShowAgain_key =
    "workbench.tasks.ignoreTask010Shown"

  public _serviceBrand: undefined
  public static OutputChannelId: string = "tasks"
  public static OutputChannelLabel: string = nls.localize("tasks", "Tasks")

  private static _nextHandle: number = 0

  private _tasksReconnected: boolean = false
  private _schemaVersion: JsonSchemaVersion | undefined
  private _executionEngine: ExecutionEngine | undefined
  private _workspaceFolders: IWorkspaceFolder[] | undefined
  private _workspace: IWorkspace | undefined
  private _ignoredWorkspaceFolders: IWorkspaceFolder[] | undefined
  private _showIgnoreMessage?: boolean
  private _providers: Map<number, ITaskProvider>
  private _providerTypes: Map<number, string>
  protected _taskSystemInfos: Map<string, ITaskSystemInfo[]>

  protected _workspaceTasksPromise?: Promise<
    Map<string, IWorkspaceFolderTaskResult>
  >
  protected readonly _whenTaskSystemReady: Promise<void>

  protected _taskSystem?: ITaskSystem
  protected _taskSystemListeners?: IDisposable[] = []
  private _recentlyUsedTasksV1: LRUCache<string, string> | undefined
  private _recentlyUsedTasks: LRUCache<string, string> | undefined

  private _persistentTasks: LRUCache<string, string> | undefined

  protected _taskRunningState: IContextKey<boolean>

  protected _outputChannel: IOutputChannel
  protected readonly _onDidStateChange: Emitter<ITaskEvent>
  private _waitForAllSupportedExecutions: Promise<void>
  private _onDidRegisterSupportedExecutions: Emitter<void> = new Emitter()
  private _onDidRegisterAllSupportedExecutions: Emitter<void> = new Emitter()
  private _onDidChangeTaskSystemInfo: Emitter<void> = new Emitter()
  private _willRestart: boolean = false
  public onDidChangeTaskSystemInfo: Event<void> =
    this._onDidChangeTaskSystemInfo.event
  private _onDidReconnectToTasks: Emitter<void> = new Emitter()
  public onDidReconnectToTasks: Event<void> = this._onDidReconnectToTasks.event
  private _onDidChangeTaskConfig: Emitter<void> = new Emitter()
  public onDidChangeTaskConfig: Event<void> = this._onDidChangeTaskConfig.event
  public get isReconnected(): boolean {
    return this._tasksReconnected
  }

  constructor(
    @IConfigurationService
    private readonly _configurationService: IConfigurationService,
    @IMarkerService protected readonly _markerService: IMarkerService,
    @IOutputService protected readonly _outputService: IOutputService,
    @IPaneCompositePartService
    private readonly _paneCompositeService: IPaneCompositePartService,
    @IViewsService private readonly _viewsService: IViewsService,
    @ICommandService private readonly _commandService: ICommandService,
    @IEditorService private readonly _editorService: IEditorService,
    @IFileService protected readonly _fileService: IFileService,
    @IWorkspaceContextService
    protected readonly _contextService: IWorkspaceContextService,
    @ITelemetryService protected readonly _telemetryService: ITelemetryService,
    @ITextFileService private readonly _textFileService: ITextFileService,
    @IModelService protected readonly _modelService: IModelService,
    @IExtensionService private readonly _extensionService: IExtensionService,
    @IQuickInputService private readonly _quickInputService: IQuickInputService,
    @IConfigurationResolverService
    protected readonly _configurationResolverService: IConfigurationResolverService,
    @ITerminalService private readonly _terminalService: ITerminalService,
    @ITerminalGroupService
    private readonly _terminalGroupService: ITerminalGroupService,
    @IStorageService private readonly _storageService: IStorageService,
    @IProgressService private readonly _progressService: IProgressService,
    @IOpenerService private readonly _openerService: IOpenerService,
    @IDialogService protected readonly _dialogService: IDialogService,
    @INotificationService
    private readonly _notificationService: INotificationService,
    @IContextKeyService
    protected readonly _contextKeyService: IContextKeyService,
    @IWorkbenchEnvironmentService
    private readonly _environmentService: IWorkbenchEnvironmentService,
    @ITerminalProfileResolverService
    private readonly _terminalProfileResolverService: ITerminalProfileResolverService,
    @IPathService private readonly _pathService: IPathService,
    @ITextModelService
    private readonly _textModelResolverService: ITextModelService,
    @IPreferencesService
    private readonly _preferencesService: IPreferencesService,
    @IViewDescriptorService
    private readonly _viewDescriptorService: IViewDescriptorService,
    @IWorkspaceTrustRequestService
    private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService,
    @IWorkspaceTrustManagementService
    private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService,
    @ILogService private readonly _logService: ILogService,
    @IThemeService private readonly _themeService: IThemeService,
    @ILifecycleService private readonly _lifecycleService: ILifecycleService,
    @IRemoteAgentService remoteAgentService: IRemoteAgentService,
    @IInstantiationService
    private readonly _instantiationService: IInstantiationService,
  ) {
    super()
    this._whenTaskSystemReady = Event.toPromise(this.onDidChangeTaskSystemInfo)
    this._workspaceTasksPromise = undefined
    this._taskSystem = undefined
    this._taskSystemListeners = undefined
    this._outputChannel = this._outputService.getChannel(
      AbstractTaskService.OutputChannelId,
    )!
    this._providers = new Map<number, ITaskProvider>()
    this._providerTypes = new Map<number, string>()
    this._taskSystemInfos = new Map<string, ITaskSystemInfo[]>()
    this._register(
      this._contextService.onDidChangeWorkspaceFolders(() => {
        const folderSetup = this._computeWorkspaceFolderSetup()
        if (this.executionEngine !== folderSetup[2]) {
          this._disposeTaskSystemListeners()
          this._taskSystem = undefined
        }
        this._updateSetup(folderSetup)
        return this._updateWorkspaceTasks(TaskRunSource.FolderOpen)
      }),
    )
    this._register(
      this._configurationService.onDidChangeConfiguration(async (e) => {
        if (
          !e.affectsConfiguration("tasks") ||
          (!this._taskSystem && !this._workspaceTasksPromise)
        ) {
          return
        }

        if (
          !this._taskSystem ||
          this._taskSystem instanceof TerminalTaskSystem
        ) {
          this._outputChannel.clear()
        }

        if (e.affectsConfiguration(TaskSettingId.Reconnection)) {
          if (
            !this._configurationService.getValue(TaskSettingId.Reconnection)
          ) {
            this._persistentTasks?.clear()
            this._storageService.remove(
              AbstractTaskService.PersistentTasks_Key,
              StorageScope.WORKSPACE,
            )
          }
        }

        this._setTaskLRUCacheLimit()
        await this._updateWorkspaceTasks(TaskRunSource.ConfigurationChange)
        this._onDidChangeTaskConfig.fire()
      }),
    )
    this._taskRunningState = TASK_RUNNING_STATE.bindTo(_contextKeyService)
    this._onDidStateChange = this._register(new Emitter())
    this._registerCommands().then(() =>
      TaskCommandsRegistered.bindTo(this._contextKeyService).set(true),
    )
    ServerlessWebContext.bindTo(this._contextKeyService).set(
      Platform.isWeb && !remoteAgentService.getConnection()?.remoteAuthority,
    )
    this._configurationResolverService.contributeVariable(
      "defaultBuildTask",
      async (): Promise<string | undefined> => {
        let tasks = await this._getTasksForGroup(TaskGroup.Build)
        if (tasks.length > 0) {
          const defaults = this._getDefaultTasks(tasks)
          if (defaults.length === 1) {
            return defaults[0]._label
          } else if (defaults.length) {
            tasks = defaults
          }
        }

        let entry: ITaskQuickPickEntry | null | undefined
        if (tasks && tasks.length > 0) {
          entry = await this._showQuickPick(
            tasks,
            nls.localize(
              "TaskService.pickBuildTaskForLabel",
              "Select the build task (there is no default build task defined)",
            ),
          )
        }

        const task: Task | undefined | null = entry ? entry.task : undefined
        if (!task) {
          return undefined
        }
        return task._label
      },
    )
    this._register(
      this._lifecycleService.onBeforeShutdown((e) => {
        this._willRestart = e.reason !== ShutdownReason.RELOAD
      }),
    )
    this._register(
      this.onDidStateChange((e) => {
        this._log(
          nls.localize("taskEvent", "Task Event kind: {0}", e.kind),
          true,
        )
        if (e.kind === TaskEventKind.Changed) {
          // no-op
        } else if (
          (this._willRestart ||
            (e.kind === TaskEventKind.Terminated &&
              e.exitReason === TerminalExitReason.User)) &&
          e.taskId
        ) {
          const key = e.__task.getKey()
          if (key) {
            this.removePersistentTask(key)
          }
        } else if (
          e.kind === TaskEventKind.Start &&
          e.__task &&
          e.__task.getWorkspaceFolder()
        ) {
          this._setPersistentTask(e.__task)
        }
      }),
    )
    this._waitForAllSupportedExecutions = new Promise((resolve) => {
      Event.once(this._onDidRegisterAllSupportedExecutions.event)(() =>
        resolve(),
      )
    })
    if (this._terminalService.getReconnectedTerminals("Task")?.length) {
      this._attemptTaskReconnection()
    } else {
      this._terminalService.whenConnected.then(() => {
        if (this._terminalService.getReconnectedTerminals("Task")?.length) {
          this._attemptTaskReconnection()
        } else {
          this._tasksReconnected = true
          this._onDidReconnectToTasks.fire()
        }
      })
    }
    this._upgrade()
  }

  public registerSupportedExecutions(
    custom?: boolean,
    shell?: boolean,
    process?: boolean,
  ) {
    if (custom !== undefined) {
      const customContext = CustomExecutionSupportedContext.bindTo(
        this._contextKeyService,
      )
      customContext.set(custom)
    }
    const isVirtual = !!VirtualWorkspaceContext.getValue(
      this._contextKeyService,
    )
    if (shell !== undefined) {
      const shellContext = ShellExecutionSupportedContext.bindTo(
        this._contextKeyService,
      )
      shellContext.set(shell && !isVirtual)
    }
    if (process !== undefined) {
      const processContext = ProcessExecutionSupportedContext.bindTo(
        this._contextKeyService,
      )
      processContext.set(process && !isVirtual)
    }
    // update tasks so an incomplete list isn't returned when getWorkspaceTasks is called
    this._workspaceTasksPromise = undefined
    this._onDidRegisterSupportedExecutions.fire()
    if (Platform.isWeb || (custom && shell && process)) {
      this._onDidRegisterAllSupportedExecutions.fire()
    }
  }

  private _attemptTaskReconnection(): void {
    if (this._lifecycleService.startupKind !== StartupKind.ReloadedWindow) {
      this._log(
        nls.localize(
          "TaskService.skippingReconnection",
          "Startup kind not window reload, setting connected and removing persistent tasks",
        ),
        true,
      )
      this._tasksReconnected = true
      this._storageService.remove(
        AbstractTaskService.PersistentTasks_Key,
        StorageScope.WORKSPACE,
      )
    }
    if (
      !this._configurationService.getValue(TaskSettingId.Reconnection) ||
      this._tasksReconnected
    ) {
      this._log(
        nls.localize(
          "TaskService.notConnecting",
          "Setting tasks connected configured value {0}, tasks were already reconnected {1}",
          this._configurationService.getValue(TaskSettingId.Reconnection),
          this._tasksReconnected,
        ),
        true,
      )
      this._tasksReconnected = true
      return
    }
    this._log(
      nls.localize(
        "TaskService.reconnecting",
        "Reconnecting to running tasks...",
      ),
      true,
    )
    this.getWorkspaceTasks(TaskRunSource.Reconnect).then(async () => {
      this._tasksReconnected = await this._reconnectTasks()
      this._log(
        nls.localize(
          "TaskService.reconnected",
          "Reconnected to running tasks.",
        ),
        true,
      )
      this._onDidReconnectToTasks.fire()
    })
  }

  private async _reconnectTasks(): Promise<boolean> {
    const tasks = await this.getSavedTasks("persistent")
    if (!tasks.length) {
      this._log(
        nls.localize(
          "TaskService.noTasks",
          "No persistent tasks to reconnect.",
        ),
        true,
      )
      return true
    }
    const taskLabels = tasks.map((task) => task._label).join(", ")
    this._log(
      nls.localize(
        "TaskService.reconnectingTasks",
        "Reconnecting to {0} tasks...",
        taskLabels,
      ),
      true,
    )
    for (const task of tasks) {
      if (ConfiguringTask.is(task)) {
        const resolved = await this.tryResolveTask(task)
        if (resolved) {
          this.run(resolved, undefined, TaskRunSource.Reconnect)
        }
      } else {
        this.run(task, undefined, TaskRunSource.Reconnect)
      }
    }
    return true
  }

  public get onDidStateChange(): Event<ITaskEvent> {
    return this._onDidStateChange.event
  }

  public get supportsMultipleTaskExecutions(): boolean {
    return this.inTerminal()
  }

  private async _registerCommands(): Promise<void> {
    CommandsRegistry.registerCommand({
      id: "workbench.action.tasks.runTask",
      handler: async (accessor, arg) => {
        if (await this._trust()) {
          await this._runTaskCommand(arg)
        }
      },
      metadata: {
        description: "Run Task",
        args: [
          {
            name: "args",
            isOptional: true,
            description: nls.localize(
              "runTask.arg",
              "Filters the tasks shown in the quickpick",
            ),
            schema: {
              anyOf: [
                {
                  type: "string",
                  description: nls.localize(
                    "runTask.label",
                    "The task's label or a term to filter by",
                  ),
                },
                {
                  type: "object",
                  properties: {
                    type: {
                      type: "string",
                      description: nls.localize(
                        "runTask.type",
                        "The contributed task type",
                      ),
                    },
                    task: {
                      type: "string",
                      description: nls.localize(
                        "runTask.task",
                        "The task's label or a term to filter by",
                      ),
                    },
                  },
                },
              ],
            },
          },
        ],
      },
    })

    CommandsRegistry.registerCommand(
      "workbench.action.tasks.reRunTask",
      async (accessor, arg) => {
        if (await this._trust()) {
          this._reRunTaskCommand()
        }
      },
    )

    CommandsRegistry.registerCommand(
      "workbench.action.tasks.restartTask",
      async (accessor, arg) => {
        if (await this._trust()) {
          this._runRestartTaskCommand(arg)
        }
      },
    )

    CommandsRegistry.registerCommand(
      "workbench.action.tasks.terminate",
      async (accessor, arg) => {
        if (await this._trust()) {
          this._runTerminateCommand(arg)
        }
      },
    )
    CommandsRegistry.registerCommand("workbench.action.tasks.showLog", () => {
      this._showOutput(undefined, true)
    })

    CommandsRegistry.registerCommand(
      "workbench.action.tasks.build",
      async () => {
        if (await this._trust()) {
          this._runBuildCommand()
        }
      },
    )

    CommandsRegistry.registerCommand(
      "workbench.action.tasks.test",
      async () => {
        if (await this._trust()) {
          this._runTestCommand()
        }
      },
    )

    CommandsRegistry.registerCommand(
      "workbench.action.tasks.configureTaskRunner",
      async () => {
        if (await this._trust()) {
          this._runConfigureTasks()
        }
      },
    )

    CommandsRegistry.registerCommand(
      "workbench.action.tasks.configureDefaultBuildTask",
      async () => {
        if (await this._trust()) {
          this._runConfigureDefaultBuildTask()
        }
      },
    )

    CommandsRegistry.registerCommand(
      "workbench.action.tasks.configureDefaultTestTask",
      async () => {
        if (await this._trust()) {
          this._runConfigureDefaultTestTask()
        }
      },
    )

    CommandsRegistry.registerCommand(
      "workbench.action.tasks.showTasks",
      async () => {
        if (await this._trust()) {
          return this.runShowTasks()
        }
      },
    )

    CommandsRegistry.registerCommand(
      "workbench.action.tasks.toggleProblems",
      () =>
        this._commandService.executeCommand(
          Markers.TOGGLE_MARKERS_VIEW_ACTION_ID,
        ),
    )

    CommandsRegistry.registerCommand(
      "workbench.action.tasks.openUserTasks",
      async () => {
        const resource = this._getResourceForKind(TaskSourceKind.User)
        if (resource) {
          this._openTaskFile(resource, TaskSourceKind.User)
        }
      },
    )

    CommandsRegistry.registerCommand(
      "workbench.action.tasks.openWorkspaceFileTasks",
      async () => {
        const resource = this._getResourceForKind(TaskSourceKind.WorkspaceFile)
        if (resource) {
          this._openTaskFile(resource, TaskSourceKind.WorkspaceFile)
        }
      },
    )
  }

  private get workspaceFolders(): IWorkspaceFolder[] {
    if (!this._workspaceFolders) {
      this._updateSetup()
    }
    return this._workspaceFolders!
  }

  private get ignoredWorkspaceFolders(): IWorkspaceFolder[] {
    if (!this._ignoredWorkspaceFolders) {
      this._updateSetup()
    }
    return this._ignoredWorkspaceFolders!
  }

  protected get executionEngine(): ExecutionEngine {
    if (this._executionEngine === undefined) {
      this._updateSetup()
    }
    return this._executionEngine!
  }

  private get schemaVersion(): JsonSchemaVersion {
    if (this._schemaVersion === undefined) {
      this._updateSetup()
    }
    return this._schemaVersion!
  }

  private get showIgnoreMessage(): boolean {
    if (this._showIgnoreMessage === undefined) {
      this._showIgnoreMessage = !this._storageService.getBoolean(
        AbstractTaskService.IgnoreTask010DonotShowAgain_key,
        StorageScope.WORKSPACE,
        false,
      )
    }
    return this._showIgnoreMessage
  }

  private _getActivationEvents(type: string | undefined): string[] {
    const result: string[] = []
    result.push("onCommand:workbench.action.tasks.runTask")
    if (type) {
      // send a specific activation event for this task type
      result.push(`onTaskType:${type}`)
    } else {
      // send activation events for all task types
      for (const definition of TaskDefinitionRegistry.all()) {
        result.push(`onTaskType:${definition.taskType}`)
      }
    }
    return result
  }

  private async _activateTaskProviders(
    type: string | undefined,
  ): Promise<void> {
    // We need to first wait for extensions to be registered because we might read
    // the `TaskDefinitionRegistry` in case `type` is `undefined`
    await this._extensionService.whenInstalledExtensionsRegistered()
    await raceTimeout(
      Promise.all(
        this._getActivationEvents(type).map((activationEvent) =>
          this._extensionService.activateByEvent(activationEvent),
        ),
      ),
      5000,
      () => console.warn("Timed out activating extensions for task providers"),
    )
  }

  private _updateSetup(
    setup?: [
      IWorkspaceFolder[],
      IWorkspaceFolder[],
      ExecutionEngine,
      JsonSchemaVersion,
      IWorkspace | undefined,
    ],
  ): void {
    if (!setup) {
      setup = this._computeWorkspaceFolderSetup()
    }
    this._workspaceFolders = setup[0]
    if (this._ignoredWorkspaceFolders) {
      if (this._ignoredWorkspaceFolders.length !== setup[1].length) {
        this._showIgnoreMessage = undefined
      } else {
        const set: Set<string> = new Set()
        this._ignoredWorkspaceFolders.forEach((folder) =>
          set.add(folder.uri.toString()),
        )
        for (const folder of setup[1]) {
          if (!set.has(folder.uri.toString())) {
            this._showIgnoreMessage = undefined
            break
          }
        }
      }
    }
    this._ignoredWorkspaceFolders = setup[1]
    this._executionEngine = setup[2]
    this._schemaVersion = setup[3]
    this._workspace = setup[4]
  }

  protected _showOutput(
    runSource: TaskRunSource = TaskRunSource.User,
    userRequested?: boolean,
  ): void {
    if (
      !VirtualWorkspaceContext.getValue(this._contextKeyService) &&
      (runSource === TaskRunSource.User ||
        runSource === TaskRunSource.ConfigurationChange)
    ) {
      if (userRequested) {
        this._outputService.showChannel(this._outputChannel.id, true)
      } else {
        this._notificationService.prompt(
          Severity.Warning,
          nls.localize(
            "taskServiceOutputPrompt",
            "There are task errors. See the output for details.",
          ),
          [
            {
              label: nls.localize("showOutput", "Show output"),
              run: () => {
                this._outputService.showChannel(this._outputChannel.id, true)
              },
            },
          ],
        )
      }
    }
  }

  protected _disposeTaskSystemListeners(): void {
    if (this._taskSystemListeners) {
      dispose(this._taskSystemListeners)
      this._taskSystemListeners = undefined
    }
  }

  public registerTaskProvider(
    provider: ITaskProvider,
    type: string,
  ): IDisposable {
    if (!provider) {
      return {
        dispose: () => {},
      }
    }
    const handle = AbstractTaskService._nextHandle++
    this._providers.set(handle, provider)
    this._providerTypes.set(handle, type)
    return {
      dispose: () => {
        this._providers.delete(handle)
        this._providerTypes.delete(handle)
      },
    }
  }

  get hasTaskSystemInfo(): boolean {
    const infosCount = Array.from(this._taskSystemInfos.values()).flat().length
    // If there's a remoteAuthority, then we end up with 2 taskSystemInfos,
    // one for each extension host.
    if (this._environmentService.remoteAuthority) {
      return infosCount > 1
    }
    return infosCount > 0
  }

  public registerTaskSystem(key: string, info: ITaskSystemInfo): void {
    // Ideally the Web caller of registerRegisterTaskSystem would use the correct key.
    // However, the caller doesn't know about the workspace folders at the time of the call, even though we know about them here.
    if (info.platform === Platform.Platform.Web) {
      key = this.workspaceFolders.length
        ? this.workspaceFolders[0].uri.scheme
        : key
    }
    if (!this._taskSystemInfos.has(key)) {
      this._taskSystemInfos.set(key, [info])
    } else {
      const infos = this._taskSystemInfos.get(key)!
      if (info.platform === Platform.Platform.Web) {
        // Web infos should be pushed last.
        infos.push(info)
      } else {
        infos.unshift(info)
      }
    }

    if (this.hasTaskSystemInfo) {
      this._onDidChangeTaskSystemInfo.fire()
    }
  }

  private _getTaskSystemInfo(key: string): ITaskSystemInfo | undefined {
    const infos = this._taskSystemInfos.get(key)
    return infos && infos.length ? infos[0] : undefined
  }

  public extensionCallbackTaskComplete(
    task: Task,
    result: number,
  ): Promise<void> {
    if (!this._taskSystem) {
      return Promise.resolve()
    }
    return this._taskSystem.customExecutionComplete(task, result)
  }

  /**
   * Get a subset of workspace tasks that match a certain predicate.
   */
  private async _findWorkspaceTasks(
    predicate: (
      task: ConfiguringTask | Task,
      workspaceFolder: IWorkspaceFolder,
    ) => boolean,
  ): Promise<(ConfiguringTask | Task)[]> {
    const result: (ConfiguringTask | Task)[] = []

    const tasks = await this.getWorkspaceTasks()
    for (const [, workspaceTasks] of tasks) {
      if (workspaceTasks.configurations) {
        for (const taskName in workspaceTasks.configurations.byIdentifier) {
          const task = workspaceTasks.configurations.byIdentifier[taskName]
          if (predicate(task, workspaceTasks.workspaceFolder)) {
            result.push(task)
          }
        }
      }
      if (workspaceTasks.set) {
        for (const task of workspaceTasks.set.tasks) {
          if (predicate(task, workspaceTasks.workspaceFolder)) {
            result.push(task)
          }
        }
      }
    }
    return result
  }

  private async _findWorkspaceTasksInGroup(
    group: TaskGroup,
    isDefault: boolean,
  ): Promise<(ConfiguringTask | Task)[]> {
    return this._findWorkspaceTasks((task) => {
      const taskGroup = task.configurationProperties.group
      if (taskGroup && typeof taskGroup !== "string") {
        return (
          taskGroup._id === group._id && (!isDefault || !!taskGroup.isDefault)
        )
      }
      return false
    })
  }

  public async getTask(
    folder: IWorkspace | IWorkspaceFolder | string,
    identifier: string | ITaskIdentifier,
    compareId: boolean = false,
    type: string | undefined = undefined,
  ): Promise<Task | undefined> {
    if (!(await this._trust())) {
      return
    }
    const name = Types.isString(folder)
      ? folder
      : isWorkspaceFolder(folder)
        ? folder.name
        : folder.configuration
          ? resources.basename(folder.configuration)
          : undefined
    if (this.ignoredWorkspaceFolders.some((ignored) => ignored.name === name)) {
      return Promise.reject(
        new Error(
          nls.localize(
            "TaskServer.folderIgnored",
            "The folder {0} is ignored since it uses task version 0.1.0",
            name,
          ),
        ),
      )
    }
    const key: string | KeyedTaskIdentifier | undefined = !Types.isString(
      identifier,
    )
      ? TaskDefinition.createTaskIdentifier(identifier, console)
      : identifier

    if (key === undefined) {
      return Promise.resolve(undefined)
    }

    // Try to find the task in the workspace
    const requestedFolder = TaskMap.getKey(folder)
    const matchedTasks = await this._findWorkspaceTasks(
      (task, workspaceFolder) => {
        const taskFolder = TaskMap.getKey(workspaceFolder)
        if (
          taskFolder !== requestedFolder &&
          taskFolder !== USER_TASKS_GROUP_KEY
        ) {
          return false
        }
        return task.matches(key, compareId)
      },
    )
    matchedTasks.sort((task) =>
      task._source.kind === TaskSourceKind.Extension ? 1 : -1,
    )
    if (matchedTasks.length > 0) {
      // Nice, we found a configured task!
      const task = matchedTasks[0]
      if (ConfiguringTask.is(task)) {
        return this.tryResolveTask(task)
      } else {
        return task
      }
    }

    // We didn't find the task, so we need to ask all resolvers about it
    const map = await this._getGroupedTasks({ type })
    let values = map.get(folder)
    values = values.concat(map.get(USER_TASKS_GROUP_KEY))

    if (!values) {
      return undefined
    }
    values = values
      .filter((task) => task.matches(key, compareId))
      .sort((task) => (task._source.kind === TaskSourceKind.Extension ? 1 : -1))
    return values.length > 0 ? values[0] : undefined
  }

  public async tryResolveTask(
    configuringTask: ConfiguringTask,
  ): Promise<Task | undefined> {
    if (!(await this._trust())) {
      return
    }
    await this._activateTaskProviders(configuringTask.type)
    let matchingProvider: ITaskProvider | undefined
    let matchingProviderUnavailable: boolean = false
    for (const [handle, provider] of this._providers) {
      const providerType = this._providerTypes.get(handle)
      if (configuringTask.type === providerType) {
        if (providerType && !this._isTaskProviderEnabled(providerType)) {
          matchingProviderUnavailable = true
          continue
        }
        matchingProvider = provider
        break
      }
    }

    if (!matchingProvider) {
      if (matchingProviderUnavailable) {
        this._log(
          nls.localize(
            "TaskService.providerUnavailable",
            "Warning: {0} tasks are unavailable in the current environment.",
            configuringTask.configures.type,
          ),
        )
      }
      return
    }

    // Try to resolve the task first
    try {
      const resolvedTask = await matchingProvider.resolveTask(configuringTask)
      if (resolvedTask && resolvedTask._id === configuringTask._id) {
        return TaskConfig.createCustomTask(resolvedTask, configuringTask)
      }
    } catch (error) {
      // Ignore errors. The task could not be provided by any of the providers.
    }

    // The task couldn't be resolved. Instead, use the less efficient provideTask.
    const tasks = await this.tasks({ type: configuringTask.type })
    for (const task of tasks) {
      if (task._id === configuringTask._id) {
        return TaskConfig.createCustomTask(
          <ContributedTask>task,
          configuringTask,
        )
      }
    }

    return
  }

  protected abstract _versionAndEngineCompatible(filter?: ITaskFilter): boolean

  public async tasks(filter?: ITaskFilter): Promise<Task[]> {
    if (!(await this._trust())) {
      return []
    }
    if (!this._versionAndEngineCompatible(filter)) {
      return Promise.resolve<Task[]>([])
    }
    return this._getGroupedTasks(filter).then((map) => {
      if (!filter || !filter.type) {
        return map.all()
      }
      const result: Task[] = []
      map.forEach((tasks) => {
        for (const task of tasks) {
          if (
            ContributedTask.is(task) &&
            (task.defines.type === filter.type ||
              task._source.label === filter.type)
          ) {
            result.push(task)
          } else if (CustomTask.is(task)) {
            if (task.type === filter.type) {
              result.push(task)
            } else {
              const customizes = task.customizes()
              if (customizes && customizes.type === filter.type) {
                result.push(task)
              }
            }
          }
        }
      })
      return result
    })
  }

  public taskTypes(): string[] {
    const types: string[] = []
    if (this._isProvideTasksEnabled()) {
      for (const definition of TaskDefinitionRegistry.all()) {
        if (this._isTaskProviderEnabled(definition.taskType)) {
          types.push(definition.taskType)
        }
      }
    }
    return types
  }

  public createSorter(): TaskSorter {
    return new TaskSorter(
      this._contextService.getWorkspace()
        ? this._contextService.getWorkspace().folders
        : [],
    )
  }

  private _isActive(): Promise<boolean> {
    if (!this._taskSystem) {
      return Promise.resolve(false)
    }
    return this._taskSystem.isActive()
  }

  public async getActiveTasks(): Promise<Task[]> {
    if (!this._taskSystem) {
      return []
    }
    return this._taskSystem.getActiveTasks()
  }

  public async getBusyTasks(): Promise<Task[]> {
    if (!this._taskSystem) {
      return []
    }
    return this._taskSystem.getBusyTasks()
  }

  public getRecentlyUsedTasksV1(): LRUCache<string, string> {
    if (this._recentlyUsedTasksV1) {
      return this._recentlyUsedTasksV1
    }
    const quickOpenHistoryLimit = this._configurationService.getValue<number>(
      QUICKOPEN_HISTORY_LIMIT_CONFIG,
    )
    this._recentlyUsedTasksV1 = new LRUCache<string, string>(
      quickOpenHistoryLimit,
    )

    const storageValue = this._storageService.get(
      AbstractTaskService.RecentlyUsedTasks_Key,
      StorageScope.WORKSPACE,
    )
    if (storageValue) {
      try {
        const values: string[] = JSON.parse(storageValue)
        if (Array.isArray(values)) {
          for (const value of values) {
            this._recentlyUsedTasksV1.set(value, value)
          }
        }
      } catch (error) {
        // Ignore. We use the empty result
      }
    }
    return this._recentlyUsedTasksV1
  }

  private _getTasksFromStorage(
    type: "persistent" | "historical",
  ): LRUCache<string, string> {
    return type === "persistent"
      ? this._getPersistentTasks()
      : this._getRecentTasks()
  }

  private _getRecentTasks(): LRUCache<string, string> {
    if (this._recentlyUsedTasks) {
      return this._recentlyUsedTasks
    }
    const quickOpenHistoryLimit = this._configurationService.getValue<number>(
      QUICKOPEN_HISTORY_LIMIT_CONFIG,
    )
    this._recentlyUsedTasks = new LRUCache<string, string>(
      quickOpenHistoryLimit,
    )

    const storageValue = this._storageService.get(
      AbstractTaskService.RecentlyUsedTasks_KeyV2,
      StorageScope.WORKSPACE,
    )
    if (storageValue) {
      try {
        const values: [string, string][] = JSON.parse(storageValue)
        if (Array.isArray(values)) {
          for (const value of values) {
            this._recentlyUsedTasks.set(value[0], value[1])
          }
        }
      } catch (error) {
        // Ignore. We use the empty result
      }
    }
    return this._recentlyUsedTasks
  }

  private _getPersistentTasks(): LRUCache<string, string> {
    if (this._persistentTasks) {
      this._log(
        nls.localize(
          "taskService.gettingCachedTasks",
          "Returning cached tasks {0}",
          this._persistentTasks.size,
        ),
        true,
      )
      return this._persistentTasks
    }
    //TODO: should this # be configurable?
    this._persistentTasks = new LRUCache<string, string>(10)
    const storageValue = this._storageService.get(
      AbstractTaskService.PersistentTasks_Key,
      StorageScope.WORKSPACE,
    )
    if (storageValue) {
      try {
        const values: [string, string][] = JSON.parse(storageValue)
        if (Array.isArray(values)) {
          for (const value of values) {
            this._persistentTasks.set(value[0], value[1])
          }
        }
      } catch (error) {
        // Ignore. We use the empty result
      }
    }
    return this._persistentTasks
  }

  private _getFolderFromTaskKey(key: string): {
    folder: string | undefined
    isWorkspaceFile: boolean | undefined
  } {
    const keyValue: { folder: string | undefined; id: string | undefined } =
      JSON.parse(key)
    return {
      folder: keyValue.folder,
      isWorkspaceFile: keyValue.id?.endsWith(TaskSourceKind.WorkspaceFile),
    }
  }

  public async getSavedTasks(
    type: "persistent" | "historical",
  ): Promise<(Task | ConfiguringTask)[]> {
    const folderMap: IStringDictionary<IWorkspaceFolder> = Object.create(null)
    this.workspaceFolders.forEach((folder) => {
      folderMap[folder.uri.toString()] = folder
    })
    const folderToTasksMap: Map<string, any> = new Map()
    const workspaceToTaskMap: Map<string, any> = new Map()
    const storedTasks = this._getTasksFromStorage(type)
    const tasks: (Task | ConfiguringTask)[] = []
    this._log(
      nls.localize(
        "taskService.getSavedTasks",
        "Fetching tasks from task storage.",
      ),
      true,
    )
    function addTaskToMap(
      map: Map<string, any>,
      folder: string | undefined,
      task: any,
    ) {
      if (folder && !map.has(folder)) {
        map.set(folder, [])
      }
      if (
        folder &&
        (folderMap[folder] || folder === USER_TASKS_GROUP_KEY) &&
        task
      ) {
        map.get(folder).push(task)
      }
    }
    for (const entry of storedTasks.entries()) {
      try {
        const key = entry[0]
        const task = JSON.parse(entry[1])
        const folderInfo = this._getFolderFromTaskKey(key)
        this._log(
          nls.localize(
            "taskService.getSavedTasks.reading",
            "Reading tasks from task storage, {0}, {1}, {2}",
            key,
            task,
            folderInfo.folder,
          ),
          true,
        )
        addTaskToMap(
          folderInfo.isWorkspaceFile ? workspaceToTaskMap : folderToTasksMap,
          folderInfo.folder,
          task,
        )
      } catch (error) {
        this._log(
          nls.localize(
            "taskService.getSavedTasks.error",
            "Fetching a task from task storage failed: {0}.",
            error,
          ),
          true,
        )
      }
    }

    const readTasksMap: Map<string, Task | ConfiguringTask> = new Map()

    async function readTasks(
      that: AbstractTaskService,
      map: Map<string, any>,
      isWorkspaceFile: boolean,
    ) {
      for (const key of map.keys()) {
        const custom: CustomTask[] = []
        const customized: IStringDictionary<ConfiguringTask> =
          Object.create(null)
        const taskConfigSource = folderMap[key]
          ? isWorkspaceFile
            ? TaskConfig.TaskConfigSource.WorkspaceFile
            : TaskConfig.TaskConfigSource.TasksJson
          : TaskConfig.TaskConfigSource.User
        await that._computeTasksForSingleConfig(
          folderMap[key] ?? (await that._getAFolder()),
          {
            version: "2.0.0",
            tasks: map.get(key),
          },
          TaskRunSource.System,
          custom,
          customized,
          taskConfigSource,
          true,
        )
        custom.forEach((task) => {
          const taskKey = task.getKey()
          if (taskKey) {
            readTasksMap.set(taskKey, task)
          }
        })
        for (const configuration in customized) {
          const taskKey = customized[configuration].getKey()
          if (taskKey) {
            readTasksMap.set(taskKey, customized[configuration])
          }
        }
      }
    }
    await readTasks(this, folderToTasksMap, false)
    await readTasks(this, workspaceToTaskMap, true)
    for (const key of storedTasks.keys()) {
      if (readTasksMap.has(key)) {
        tasks.push(readTasksMap.get(key)!)
        this._log(
          nls.localize(
            "taskService.getSavedTasks.resolved",
            "Resolved task {0}",
            key,
          ),
          true,
        )
      } else {
        this._log(
          nls.localize(
            "taskService.getSavedTasks.unresolved",
            "Unable to resolve task {0} ",
            key,
          ),
          true,
        )
      }
    }
    return tasks
  }

  public removeRecentlyUsedTask(taskRecentlyUsedKey: string) {
    if (this._getTasksFromStorage("historical").has(taskRecentlyUsedKey)) {
      this._getTasksFromStorage("historical").delete(taskRecentlyUsedKey)
      this._saveRecentlyUsedTasks()
    }
  }

  public removePersistentTask(key: string) {
    this._log(
      nls.localize(
        "taskService.removePersistentTask",
        "Removing persistent task {0}",
        key,
      ),
      true,
    )
    if (this._getTasksFromStorage("persistent").has(key)) {
      this._getTasksFromStorage("persistent").delete(key)
      this._savePersistentTasks()
    }
  }

  private _setTaskLRUCacheLimit() {
    const quickOpenHistoryLimit = this._configurationService.getValue<number>(
      QUICKOPEN_HISTORY_LIMIT_CONFIG,
    )
    if (this._recentlyUsedTasks) {
      this._recentlyUsedTasks.limit = quickOpenHistoryLimit
    }
  }

  private async _setRecentlyUsedTask(task: Task): Promise<void> {
    let key = task.getKey()
    if (!InMemoryTask.is(task) && key) {
      const customizations = this._createCustomizableTask(task)
      if (ContributedTask.is(task) && customizations) {
        const custom: CustomTask[] = []
        const customized: IStringDictionary<ConfiguringTask> =
          Object.create(null)
        await this._computeTasksForSingleConfig(
          task._source.workspaceFolder ?? this.workspaceFolders[0],
          {
            version: "2.0.0",
            tasks: [customizations],
          },
          TaskRunSource.System,
          custom,
          customized,
          TaskConfig.TaskConfigSource.TasksJson,
          true,
        )
        for (const configuration in customized) {
          key = customized[configuration].getKey()!
        }
      }
      this._getTasksFromStorage("historical").set(
        key,
        JSON.stringify(customizations),
      )
      this._saveRecentlyUsedTasks()
    }
  }

  private _saveRecentlyUsedTasks(): void {
    if (!this._recentlyUsedTasks) {
      return
    }
    const quickOpenHistoryLimit = this._configurationService.getValue<number>(
      QUICKOPEN_HISTORY_LIMIT_CONFIG,
    )
    // setting history limit to 0 means no LRU sorting
    if (quickOpenHistoryLimit === 0) {
      return
    }
    let keys = [...this._recentlyUsedTasks.keys()]
    if (keys.length > quickOpenHistoryLimit) {
      keys = keys.slice(0, quickOpenHistoryLimit)
    }
    const keyValues: [string, string][] = []
    for (const key of keys) {
      keyValues.push([key, this._recentlyUsedTasks.get(key, Touch.None)!])
    }
    this._storageService.store(
      AbstractTaskService.RecentlyUsedTasks_KeyV2,
      JSON.stringify(keyValues),
      StorageScope.WORKSPACE,
      StorageTarget.MACHINE,
    )
  }

  private async _setPersistentTask(task: Task): Promise<void> {
    if (!this._configurationService.getValue(TaskSettingId.Reconnection)) {
      return
    }
    let key = task.getKey()
    if (!InMemoryTask.is(task) && key) {
      const customizations = this._createCustomizableTask(task)
      if (ContributedTask.is(task) && customizations) {
        const custom: CustomTask[] = []
        const customized: IStringDictionary<ConfiguringTask> =
          Object.create(null)
        await this._computeTasksForSingleConfig(
          task._source.workspaceFolder ?? this.workspaceFolders[0],
          {
            version: "2.0.0",
            tasks: [customizations],
          },
          TaskRunSource.System,
          custom,
          customized,
          TaskConfig.TaskConfigSource.TasksJson,
          true,
        )
        for (const configuration in customized) {
          key = customized[configuration].getKey()!
        }
      }
      if (!task.configurationProperties.isBackground) {
        return
      }
      this._log(
        nls.localize(
          "taskService.setPersistentTask",
          "Setting persistent task {0}",
          key,
        ),
        true,
      )
      this._getTasksFromStorage("persistent").set(
        key,
        JSON.stringify(customizations),
      )
      this._savePersistentTasks()
    }
  }

  private _savePersistentTasks(): void {
    this._persistentTasks = this._getTasksFromStorage("persistent")
    const keys = [...this._persistentTasks.keys()]
    const keyValues: [string, string][] = []
    for (const key of keys) {
      keyValues.push([key, this._persistentTasks.get(key, Touch.None)!])
    }
    this._log(
      nls.localize(
        "savePersistentTask",
        "Saving persistent tasks: {0}",
        keys.join(", "),
      ),
      true,
    )
    this._storageService.store(
      AbstractTaskService.PersistentTasks_Key,
      JSON.stringify(keyValues),
      StorageScope.WORKSPACE,
      StorageTarget.MACHINE,
    )
  }

  private _openDocumentation(): void {
    this._openerService.open(
      URI.parse(
        "https://code.visualstudio.com/docs/editor/tasks#_defining-a-problem-matcher",
      ),
    )
  }

  private async _findSingleWorkspaceTaskOfGroup(
    group: TaskGroup,
  ): Promise<ITaskSummary | undefined> {
    const tasksOfGroup = await this._findWorkspaceTasksInGroup(group, true)
    if (
      tasksOfGroup.length === 1 &&
      typeof tasksOfGroup[0].configurationProperties.group !== "string" &&
      tasksOfGroup[0].configurationProperties.group?.isDefault
    ) {
      let resolvedTask: Task | undefined
      if (ConfiguringTask.is(tasksOfGroup[0])) {
        resolvedTask = await this.tryResolveTask(tasksOfGroup[0])
      } else {
        resolvedTask = tasksOfGroup[0]
      }
      if (resolvedTask) {
        return this.run(resolvedTask, undefined, TaskRunSource.User)
      }
    }
    return undefined
  }

  private async _build(): Promise<ITaskSummary> {
    const tryBuildShortcut = await this._findSingleWorkspaceTaskOfGroup(
      TaskGroup.Build,
    )
    if (tryBuildShortcut) {
      return tryBuildShortcut
    }
    return this._getGroupedTasksAndExecute()
  }

  private async _runTest(): Promise<ITaskSummary> {
    const tryTestShortcut = await this._findSingleWorkspaceTaskOfGroup(
      TaskGroup.Test,
    )
    if (tryTestShortcut) {
      return tryTestShortcut
    }

    return this._getGroupedTasksAndExecute(true)
  }

  private async _getGroupedTasksAndExecute(
    test?: boolean,
  ): Promise<ITaskSummary> {
    const tasks = await this._getGroupedTasks()
    const runnable = this._createRunnableTask(
      tasks,
      test ? TaskGroup.Test : TaskGroup.Build,
    )
    if (!runnable || !runnable.task) {
      if (test) {
        if (this.schemaVersion === JsonSchemaVersion.V0_1_0) {
          throw new TaskError(
            Severity.Info,
            nls.localize(
              "TaskService.noTestTask1",
              "No test task defined. Mark a task with 'isTestCommand' in the tasks.json file.",
            ),
            TaskErrors.NoTestTask,
          )
        } else {
          throw new TaskError(
            Severity.Info,
            nls.localize(
              "TaskService.noTestTask2",
              "No test task defined. Mark a task with as a 'test' group in the tasks.json file.",
            ),
            TaskErrors.NoTestTask,
          )
        }
      } else {
        if (this.schemaVersion === JsonSchemaVersion.V0_1_0) {
          throw new TaskError(
            Severity.Info,
            nls.localize(
              "TaskService.noBuildTask1",
              "No build task defined. Mark a task with 'isBuildCommand' in the tasks.json file.",
            ),
            TaskErrors.NoBuildTask,
          )
        } else {
          throw new TaskError(
            Severity.Info,
            nls.localize(
              "TaskService.noBuildTask2",
              "No build task defined. Mark a task with as a 'build' group in the tasks.json file.",
            ),
            TaskErrors.NoBuildTask,
          )
        }
      }
    }
    let executeTaskResult: ITaskSummary
    try {
      executeTaskResult = await this._executeTask(
        runnable.task,
        runnable.resolver,
        TaskRunSource.User,
      )
    } catch (error) {
      this._handleError(error)
      return Promise.reject(error)
    }
    return executeTaskResult
  }

  public async run(
    task: Task | undefined,
    options?: IProblemMatcherRunOptions,
    runSource: TaskRunSource = TaskRunSource.System,
  ): Promise<ITaskSummary | undefined> {
    if (!(await this._trust())) {
      return
    }
    if (!task) {
      throw new TaskError(
        Severity.Info,
        nls.localize("TaskServer.noTask", "Task to execute is undefined"),
        TaskErrors.TaskNotFound,
      )
    }
    const resolver = this._createResolver()
    let executeTaskResult: ITaskSummary | undefined
    try {
      if (
        options &&
        options.attachProblemMatcher &&
        this._shouldAttachProblemMatcher(task) &&
        !InMemoryTask.is(task)
      ) {
        const taskToExecute = await this._attachProblemMatcher(task)
        if (taskToExecute) {
          executeTaskResult = await this._executeTask(
            taskToExecute,
            resolver,
            runSource,
          )
        }
      } else {
        executeTaskResult = await this._executeTask(task, resolver, runSource)
      }
      return executeTaskResult
    } catch (error) {
      this._handleError(error)
      return Promise.reject(error)
    }
  }

  private _isProvideTasksEnabled(): boolean {
    const settingValue = this._configurationService.getValue(
      TaskSettingId.AutoDetect,
    )
    return settingValue === "on"
  }

  private _isProblemMatcherPromptEnabled(type?: string): boolean {
    const settingValue = this._configurationService.getValue(
      PROBLEM_MATCHER_NEVER_CONFIG,
    )
    if (Types.isBoolean(settingValue)) {
      return !settingValue
    }
    if (type === undefined) {
      return true
    }
    const settingValueMap: IStringDictionary<boolean> = settingValue as any
    return !settingValueMap[type]
  }

  private _getTypeForTask(task: Task): string {
    let type: string
    if (CustomTask.is(task)) {
      const configProperties: TaskConfig.IConfigurationProperties =
        task._source.config.element
      type = (<any>configProperties).type
    } else {
      type = task.getDefinition()!.type
    }
    return type
  }

  private _shouldAttachProblemMatcher(task: Task): boolean {
    const enabled = this._isProblemMatcherPromptEnabled(
      this._getTypeForTask(task),
    )
    if (enabled === false) {
      return false
    }
    if (!this._canCustomize(task)) {
      return false
    }
    if (
      task.configurationProperties.group !== undefined &&
      task.configurationProperties.group !== TaskGroup.Build
    ) {
      return false
    }
    if (
      task.configurationProperties.problemMatchers !== undefined &&
      task.configurationProperties.problemMatchers.length > 0
    ) {
      return false
    }
    if (ContributedTask.is(task)) {
      return (
        !task.hasDefinedMatchers &&
        !!task.configurationProperties.problemMatchers &&
        task.configurationProperties.problemMatchers.length === 0
      )
    }
    if (CustomTask.is(task)) {
      const configProperties: TaskConfig.IConfigurationProperties =
        task._source.config.element
      return (
        configProperties.problemMatcher === undefined &&
        !task.hasDefinedMatchers
      )
    }
    return false
  }

  private async _updateNeverProblemMatcherSetting(type: string): Promise<void> {
    const current = this._configurationService.getValue(
      PROBLEM_MATCHER_NEVER_CONFIG,
    )
    if (current === true) {
      return
    }
    let newValue: IStringDictionary<boolean>
    if (current !== false) {
      newValue = <any>current
    } else {
      newValue = Object.create(null)
    }
    newValue[type] = true
    return this._configurationService.updateValue(
      PROBLEM_MATCHER_NEVER_CONFIG,
      newValue,
    )
  }

  private async _attachProblemMatcher(
    task: ContributedTask | CustomTask,
  ): Promise<Task | undefined> {
    interface IProblemMatcherPickEntry extends IQuickPickItem {
      matcher: INamedProblemMatcher | undefined
      never?: boolean
      learnMore?: boolean
      setting?: string
    }
    let entries: QuickPickInput<IProblemMatcherPickEntry>[] = []
    for (const key of ProblemMatcherRegistry.keys()) {
      const matcher = ProblemMatcherRegistry.get(key)
      if (matcher.deprecated) {
        continue
      }
      if (matcher.name === matcher.label) {
        entries.push({ label: matcher.name, matcher: matcher })
      } else {
        entries.push({
          label: matcher.label,
          description: `$${matcher.name}`,
          matcher: matcher,
        })
      }
    }
    if (entries.length === 0) {
      return
    }
    entries = entries.sort((a, b) => {
      if (a.label && b.label) {
        return a.label.localeCompare(b.label)
      } else {
        return 0
      }
    })
    entries.unshift({
      type: "separator",
      label: nls.localize("TaskService.associate", "associate"),
    })
    let taskType: string
    if (CustomTask.is(task)) {
      const configProperties: TaskConfig.IConfigurationProperties =
        task._source.config.element
      taskType = (<any>configProperties).type
    } else {
      taskType = task.getDefinition().type
    }
    entries.unshift(
      {
        label: nls.localize(
          "TaskService.attachProblemMatcher.continueWithout",
          "Continue without scanning the task output",
        ),
        matcher: undefined,
      },
      {
        label: nls.localize(
          "TaskService.attachProblemMatcher.never",
          "Never scan the task output for this task",
        ),
        matcher: undefined,
        never: true,
      },
      {
        label: nls.localize(
          "TaskService.attachProblemMatcher.neverType",
          "Never scan the task output for {0} tasks",
          taskType,
        ),
        matcher: undefined,
        setting: taskType,
      },
      {
        label: nls.localize(
          "TaskService.attachProblemMatcher.learnMoreAbout",
          "Learn more about scanning the task output",
        ),
        matcher: undefined,
        learnMore: true,
      },
    )
    const problemMatcher = await this._quickInputService.pick(entries, {
      placeHolder: nls.localize(
        "selectProblemMatcher",
        "Select for which kind of errors and warnings to scan the task output",
      ),
    })
    if (!problemMatcher) {
      return task
    }
    if (problemMatcher.learnMore) {
      this._openDocumentation()
      return undefined
    }
    if (problemMatcher.never) {
      this.customize(task, { problemMatcher: [] }, true)
      return task
    }
    if (problemMatcher.matcher) {
      const newTask = task.clone()
      const matcherReference = `$${problemMatcher.matcher.name}`
      const properties: ICustomizationProperties = {
        problemMatcher: [matcherReference],
      }
      newTask.configurationProperties.problemMatchers = [matcherReference]
      const matcher = ProblemMatcherRegistry.get(problemMatcher.matcher.name)
      if (matcher && matcher.watching !== undefined) {
        properties.isBackground = true
        newTask.configurationProperties.isBackground = true
      }
      this.customize(task, properties, true)
      return newTask
    }
    if (problemMatcher.setting) {
      await this._updateNeverProblemMatcherSetting(problemMatcher.setting)
    }
    return task
  }

  private async _getTasksForGroup(group: TaskGroup): Promise<Task[]> {
    const groups = await this._getGroupedTasks()
    const result: Task[] = []
    groups.forEach((tasks) => {
      for (const task of tasks) {
        const configTaskGroup = TaskGroup.from(
          task.configurationProperties.group,
        )
        if (configTaskGroup?._id === group._id) {
          result.push(task)
        }
      }
    })
    return result
  }

  public needsFolderQualification(): boolean {
    return this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE
  }

  private _canCustomize(task: Task): boolean {
    if (this.schemaVersion !== JsonSchemaVersion.V2_0_0) {
      return false
    }
    if (CustomTask.is(task)) {
      return true
    }
    if (ContributedTask.is(task)) {
      return !!task.getWorkspaceFolder()
    }
    return false
  }

  private async _formatTaskForJson(
    resource: URI,
    task: TaskConfig.ICustomTask | TaskConfig.IConfiguringTask,
  ): Promise<string> {
    let reference: IReference<IResolvedTextEditorModel> | undefined
    let stringValue: string = ""
    try {
      reference =
        await this._textModelResolverService.createModelReference(resource)
      const model = reference.object.textEditorModel
      const { tabSize, insertSpaces } = model.getOptions()
      const eol = model.getEOL()
      let stringified = toFormattedString(task, { eol, tabSize, insertSpaces })
      const regex = new RegExp(
        eol + (insertSpaces ? " ".repeat(tabSize) : "\\t"),
        "g",
      )
      stringified = stringified.replace(
        regex,
        eol + (insertSpaces ? " ".repeat(tabSize * 3) : "\t\t\t"),
      )
      const twoTabs = insertSpaces ? " ".repeat(tabSize * 2) : "\t\t"
      stringValue =
        twoTabs +
        stringified.slice(0, stringified.length - 1) +
        twoTabs +
        stringified.slice(stringified.length - 1)
    } finally {
      reference?.dispose()
    }
    return stringValue
  }

  private async _openEditorAtTask(
    resource: URI | undefined,
    task:
      | TaskConfig.ICustomTask
      | TaskConfig.IConfiguringTask
      | string
      | undefined,
    configIndex: number = -1,
  ): Promise<boolean> {
    if (resource === undefined) {
      return Promise.resolve(false)
    }
    const fileContent = await this._fileService.readFile(resource)
    const content = fileContent.value
    if (!content || !task) {
      return false
    }
    const contentValue = content.toString()
    let stringValue: string | undefined
    if (configIndex !== -1) {
      const json: TaskConfig.IExternalTaskRunnerConfiguration =
        this._configurationService.getValue<TaskConfig.IExternalTaskRunnerConfiguration>(
          "tasks",
          { resource },
        )
      if (json.tasks && json.tasks.length > configIndex) {
        stringValue = await this._formatTaskForJson(
          resource,
          json.tasks[configIndex],
        )
      }
    }
    if (!stringValue) {
      if (typeof task === "string") {
        stringValue = task
      } else {
        stringValue = await this._formatTaskForJson(resource, task)
      }
    }

    const index = contentValue.indexOf(stringValue)
    let startLineNumber = 1
    for (let i = 0; i < index; i++) {
      if (contentValue.charAt(i) === "\n") {
        startLineNumber++
      }
    }
    let endLineNumber = startLineNumber
    for (let i = 0; i < stringValue.length; i++) {
      if (stringValue.charAt(i) === "\n") {
        endLineNumber++
      }
    }
    const selection =
      startLineNumber > 1
        ? {
            startLineNumber,
            startColumn: startLineNumber === endLineNumber ? 4 : 3,
            endLineNumber,
            endColumn: startLineNumber === endLineNumber ? undefined : 4,
          }
        : undefined

    await this._editorService.openEditor({
      resource,
      options: {
        pinned: false,
        forceReload: true, // because content might have changed
        selection,
        selectionRevealType:
          TextEditorSelectionRevealType.CenterIfOutsideViewport,
      },
    })
    return !!selection
  }

  private _createCustomizableTask(
    task: ContributedTask | CustomTask | ConfiguringTask,
  ): TaskConfig.ICustomTask | TaskConfig.IConfiguringTask | undefined {
    let toCustomize:
      | TaskConfig.ICustomTask
      | TaskConfig.IConfiguringTask
      | undefined
    const taskConfig =
      CustomTask.is(task) || ConfiguringTask.is(task)
        ? task._source.config
        : undefined
    if (taskConfig && taskConfig.element) {
      toCustomize = { ...taskConfig.element }
    } else if (ContributedTask.is(task)) {
      toCustomize = {}
      const identifier: TaskConfig.ITaskIdentifier = Object.assign(
        Object.create(null),
        task.defines,
      )
      delete identifier["_key"]
      Object.keys(identifier).forEach(
        (key) => ((<any>toCustomize)![key] = identifier[key]),
      )
      if (
        task.configurationProperties.problemMatchers &&
        task.configurationProperties.problemMatchers.length > 0 &&
        Types.isStringArray(task.configurationProperties.problemMatchers)
      ) {
        toCustomize.problemMatcher =
          task.configurationProperties.problemMatchers
      }
      if (task.configurationProperties.group) {
        toCustomize.group = TaskConfig.GroupKind.to(
          task.configurationProperties.group,
        )
      }
    }
    if (!toCustomize) {
      return undefined
    }
    if (
      (toCustomize.problemMatcher === undefined &&
        task.configurationProperties.problemMatchers === undefined) ||
      (task.configurationProperties.problemMatchers &&
        task.configurationProperties.problemMatchers.length === 0)
    ) {
      toCustomize.problemMatcher = []
    }
    if (task._source.label !== "Workspace") {
      toCustomize.label = task.configurationProperties.identifier
    } else {
      toCustomize.label = task._label
    }
    toCustomize.detail = task.configurationProperties.detail
    return toCustomize
  }

  public async customize(
    task: ContributedTask | CustomTask | ConfiguringTask,
    properties?: ICustomizationProperties,
    openConfig?: boolean,
  ): Promise<void> {
    if (!(await this._trust())) {
      return
    }

    const workspaceFolder = task.getWorkspaceFolder()
    if (!workspaceFolder) {
      return Promise.resolve(undefined)
    }
    const configuration = this._getConfiguration(
      workspaceFolder,
      task._source.kind,
    )
    if (configuration.hasParseErrors) {
      this._notificationService.warn(
        nls.localize(
          "customizeParseErrors",
          "The current task configuration has errors. Please fix the errors first before customizing a task.",
        ),
      )
      return Promise.resolve<void>(undefined)
    }

    const fileConfig = configuration.config
    const toCustomize = this._createCustomizableTask(task)
    if (!toCustomize) {
      return Promise.resolve(undefined)
    }
    const index: number | undefined = CustomTask.is(task)
      ? task._source.config.index
      : undefined
    if (properties) {
      for (const property of Object.getOwnPropertyNames(properties)) {
        const value = (<any>properties)[property]
        if (value !== undefined && value !== null) {
          ;(<any>toCustomize)[property] = value
        }
      }
    }

    if (!fileConfig) {
      const value = {
        version: "2.0.0",
        tasks: [toCustomize],
      }
      let content =
        [
          "{",
          nls.localize(
            "tasksJsonComment",
            "\t// See https://go.microsoft.com/fwlink/?LinkId=733558 \n\t// for the documentation about the tasks.json format",
          ),
        ].join("\n") + JSON.stringify(value, null, "\t").substr(1)
      const editorConfig = this._configurationService.getValue<any>()
      if (editorConfig.editor.insertSpaces) {
        content = content.replace(
          /(\n)(\t+)/g,
          (_, s1, s2) =>
            s1 + " ".repeat(s2.length * editorConfig.editor.tabSize),
        )
      }
      await this._textFileService.create([
        {
          resource: workspaceFolder.toResource(".vscode/tasks.json"),
          value: content,
        },
      ])
    } else {
      // We have a global task configuration
      if (index === -1 && properties) {
        if (properties.problemMatcher !== undefined) {
          fileConfig.problemMatcher = properties.problemMatcher
          await this._writeConfiguration(
            workspaceFolder,
            "tasks.problemMatchers",
            fileConfig.problemMatcher,
            task._source.kind,
          )
        } else if (properties.group !== undefined) {
          fileConfig.group = properties.group
          await this._writeConfiguration(
            workspaceFolder,
            "tasks.group",
            fileConfig.group,
            task._source.kind,
          )
        }
      } else {
        if (!Array.isArray(fileConfig.tasks)) {
          fileConfig.tasks = []
        }
        if (index === undefined) {
          fileConfig.tasks.push(toCustomize)
        } else {
          fileConfig.tasks[index] = toCustomize
        }
        await this._writeConfiguration(
          workspaceFolder,
          "tasks.tasks",
          fileConfig.tasks,
          task._source.kind,
        )
      }
    }

    if (openConfig) {
      this._openEditorAtTask(this._getResourceForTask(task), toCustomize)
    }
  }

  private _writeConfiguration(
    workspaceFolder: IWorkspaceFolder,
    key: string,
    value: any,
    source?: string,
  ): Promise<void> | undefined {
    let target: ConfigurationTarget | undefined = undefined
    switch (source) {
      case TaskSourceKind.User:
        target = ConfigurationTarget.USER
        break
      case TaskSourceKind.WorkspaceFile:
        target = ConfigurationTarget.WORKSPACE
        break
      default:
        if (
          this._contextService.getWorkbenchState() === WorkbenchState.FOLDER
        ) {
          target = ConfigurationTarget.WORKSPACE
        } else if (
          this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE
        ) {
          target = ConfigurationTarget.WORKSPACE_FOLDER
        }
    }
    if (target) {
      return this._configurationService.updateValue(
        key,
        value,
        { resource: workspaceFolder.uri },
        target,
      )
    } else {
      return undefined
    }
  }

  private _getResourceForKind(kind: string): URI | undefined {
    this._updateSetup()
    switch (kind) {
      case TaskSourceKind.User: {
        return resources.joinPath(
          resources.dirname(this._preferencesService.userSettingsResource),
          "tasks.json",
        )
      }
      case TaskSourceKind.WorkspaceFile: {
        if (this._workspace && this._workspace.configuration) {
          return this._workspace.configuration
        }
      }
      default: {
        return undefined
      }
    }
  }

  private _getResourceForTask(
    task: CustomTask | ConfiguringTask | ContributedTask,
  ): URI {
    if (CustomTask.is(task)) {
      let uri = this._getResourceForKind(task._source.kind)
      if (!uri) {
        const taskFolder = task.getWorkspaceFolder()
        if (taskFolder) {
          uri = taskFolder.toResource(task._source.config.file)
        } else {
          uri = this.workspaceFolders[0].uri
        }
      }
      return uri
    } else {
      return task.getWorkspaceFolder()!.toResource(".vscode/tasks.json")
    }
  }

  public async openConfig(
    task: CustomTask | ConfiguringTask | undefined,
  ): Promise<boolean> {
    let resource: URI | undefined
    if (task) {
      resource = this._getResourceForTask(task)
    } else {
      resource =
        this._workspaceFolders && this._workspaceFolders.length > 0
          ? this._workspaceFolders[0].toResource(".vscode/tasks.json")
          : undefined
    }
    return this._openEditorAtTask(
      resource,
      task ? task._label : undefined,
      task ? task._source.config.index : -1,
    )
  }

  private _createRunnableTask(
    tasks: TaskMap,
    group: TaskGroup,
  ): { task: Task; resolver: ITaskResolver } | undefined {
    interface IResolverData {
      id: Map<string, Task>
      label: Map<string, Task>
      identifier: Map<string, Task>
    }

    const resolverData: Map<string, IResolverData> = new Map()
    const workspaceTasks: Task[] = []
    const extensionTasks: Task[] = []
    tasks.forEach((tasks, folder) => {
      let data = resolverData.get(folder)
      if (!data) {
        data = {
          id: new Map<string, Task>(),
          label: new Map<string, Task>(),
          identifier: new Map<string, Task>(),
        }
        resolverData.set(folder, data)
      }
      for (const task of tasks) {
        data.id.set(task._id, task)
        data.label.set(task._label, task)
        if (task.configurationProperties.identifier) {
          data.identifier.set(task.configurationProperties.identifier, task)
        }
        if (group && task.configurationProperties.group === group) {
          if (task._source.kind === TaskSourceKind.Workspace) {
            workspaceTasks.push(task)
          } else {
            extensionTasks.push(task)
          }
        }
      }
    })
    const resolver: ITaskResolver = {
      resolve: async (uri: URI | string, alias: string) => {
        const data = resolverData.get(
          typeof uri === "string" ? uri : uri.toString(),
        )
        if (!data) {
          return undefined
        }
        return (
          data.id.get(alias) ||
          data.label.get(alias) ||
          data.identifier.get(alias)
        )
      },
    }
    if (workspaceTasks.length > 0) {
      if (workspaceTasks.length > 1) {
        this._log(
          nls.localize(
            "moreThanOneBuildTask",
            "There are many build tasks defined in the tasks.json. Executing the first one.",
          ),
        )
      }
      return { task: workspaceTasks[0], resolver }
    }
    if (extensionTasks.length === 0) {
      return undefined
    }

    // We can only have extension tasks if we are in version 2.0.0. Then we can even run
    // multiple build tasks.
    if (extensionTasks.length === 1) {
      return { task: extensionTasks[0], resolver }
    } else {
      const id: string = UUID.generateUuid()
      const task: InMemoryTask = new InMemoryTask(
        id,
        { kind: TaskSourceKind.InMemory, label: "inMemory" },
        id,
        "inMemory",
        { reevaluateOnRerun: true },
        {
          identifier: id,
          dependsOn: extensionTasks.map((extensionTask) => {
            return {
              uri: extensionTask.getWorkspaceFolder()!.uri,
              task: extensionTask._id,
            }
          }),
          name: id,
        },
      )
      return { task, resolver }
    }
  }

  private _createResolver(grouped?: TaskMap): ITaskResolver {
    interface ResolverData {
      label: Map<string, Task>
      identifier: Map<string, Task>
      taskIdentifier: Map<string, Task>
    }

    let resolverData: Map<string, ResolverData> | undefined

    async function quickResolve(
      that: AbstractTaskService,
      uri: URI | string,
      identifier: string | ITaskIdentifier,
    ) {
      const foundTasks = await that._findWorkspaceTasks(
        (task: Task | ConfiguringTask): boolean => {
          const taskUri =
            ConfiguringTask.is(task) || CustomTask.is(task)
              ? task._source.config.workspaceFolder?.uri
              : undefined
          const originalUri = typeof uri === "string" ? uri : uri.toString()
          if (taskUri?.toString() !== originalUri) {
            return false
          }
          if (Types.isString(identifier)) {
            return (
              task._label === identifier ||
              task.configurationProperties.identifier === identifier
            )
          } else {
            const keyedIdentifier = task.getDefinition(true)
            const searchIdentifier = TaskDefinition.createTaskIdentifier(
              identifier,
              console,
            )
            return searchIdentifier && keyedIdentifier
              ? searchIdentifier._key === keyedIdentifier._key
              : false
          }
        },
      )
      if (foundTasks.length === 0) {
        return undefined
      }
      const task = foundTasks[0]
      if (ConfiguringTask.is(task)) {
        return that.tryResolveTask(task)
      }
      return task
    }

    async function getResolverData(that: AbstractTaskService) {
      if (resolverData === undefined) {
        resolverData = new Map()
        ;(grouped || (await that._getGroupedTasks())).forEach(
          (tasks, folder) => {
            let data = resolverData!.get(folder)
            if (!data) {
              data = {
                label: new Map<string, Task>(),
                identifier: new Map<string, Task>(),
                taskIdentifier: new Map<string, Task>(),
              }
              resolverData!.set(folder, data)
            }
            for (const task of tasks) {
              data.label.set(task._label, task)
              if (task.configurationProperties.identifier) {
                data.identifier.set(
                  task.configurationProperties.identifier,
                  task,
                )
              }
              const keyedIdentifier = task.getDefinition(true)
              if (keyedIdentifier !== undefined) {
                data.taskIdentifier.set(keyedIdentifier._key, task)
              }
            }
          },
        )
      }
      return resolverData
    }

    async function fullResolve(
      that: AbstractTaskService,
      uri: URI | string,
      identifier: string | ITaskIdentifier,
    ) {
      const allResolverData = await getResolverData(that)
      const data = allResolverData.get(
        typeof uri === "string" ? uri : uri.toString(),
      )
      if (!data) {
        return undefined
      }
      if (Types.isString(identifier)) {
        return data.label.get(identifier) || data.identifier.get(identifier)
      } else {
        const key = TaskDefinition.createTaskIdentifier(identifier, console)
        return key !== undefined ? data.taskIdentifier.get(key._key) : undefined
      }
    }

    return {
      resolve: async (
        uri: URI | string,
        identifier: string | ITaskIdentifier | undefined,
      ) => {
        if (!identifier) {
          return undefined
        }
        if (resolverData === undefined && grouped === undefined) {
          return (
            (await quickResolve(this, uri, identifier)) ??
            fullResolve(this, uri, identifier)
          )
        } else {
          return fullResolve(this, uri, identifier)
        }
      },
    }
  }

  private async _saveBeforeRun(): Promise<boolean> {
    enum SaveBeforeRunConfigOptions {
      Always = "always",
      Never = "never",
      Prompt = "prompt",
    }

    const saveBeforeRunTaskConfig: SaveBeforeRunConfigOptions =
      this._configurationService.getValue(TaskSettingId.SaveBeforeRun)

    if (saveBeforeRunTaskConfig === SaveBeforeRunConfigOptions.Never) {
      return false
    } else if (
      saveBeforeRunTaskConfig === SaveBeforeRunConfigOptions.Prompt &&
      this._editorService.editors.some((e) => e.isDirty())
    ) {
      const { confirmed } = await this._dialogService.confirm({
        message: nls.localize(
          "TaskSystem.saveBeforeRun.prompt.title",
          "Save all editors?",
        ),
        detail: nls.localize(
          "detail",
          "Do you want to save all editors before running the task?",
        ),
        primaryButton: nls.localize(
          { key: "saveBeforeRun.save", comment: ["&& denotes a mnemonic"] },
          "&&Save",
        ),
        cancelButton: nls.localize(
          { key: "saveBeforeRun.dontSave", comment: ["&& denotes a mnemonic"] },
          "Do&&n't Save",
        ),
      })

      if (!confirmed) {
        return false
      }
    }
    await this._editorService.saveAll({ reason: SaveReason.AUTO })
    return true
  }

  private async _executeTask(
    task: Task,
    resolver: ITaskResolver,
    runSource: TaskRunSource,
  ): Promise<ITaskSummary> {
    let taskToRun: Task = task
    if (await this._saveBeforeRun()) {
      await this._configurationService.reloadConfiguration()
      await this._updateWorkspaceTasks()
      const taskFolder = task.getWorkspaceFolder()
      const taskIdentifier = task.configurationProperties.identifier
      const taskType = CustomTask.is(task)
        ? task.customizes()?.type
        : ContributedTask.is(task)
          ? task.type
          : undefined
      // Since we save before running tasks, the task may have changed as part of the save.
      // However, if the TaskRunSource is not User, then we shouldn't try to fetch the task again
      // since this can cause a new'd task to get overwritten with a provided task.
      taskToRun =
        (taskFolder && taskIdentifier && runSource === TaskRunSource.User
          ? await this.getTask(taskFolder, taskIdentifier, false, taskType)
          : task) ?? task
    }
    await ProblemMatcherRegistry.onReady()
    const executeResult =
      runSource === TaskRunSource.Reconnect
        ? this._getTaskSystem().reconnect(taskToRun, resolver)
        : this._getTaskSystem().run(taskToRun, resolver)
    if (executeResult) {
      return this._handleExecuteResult(executeResult, runSource)
    }
    return { exitCode: 0 }
  }

  private async _handleExecuteResult(
    executeResult: ITaskExecuteResult,
    runSource?: TaskRunSource,
  ): Promise<ITaskSummary> {
    if (runSource === TaskRunSource.User) {
      await this._setRecentlyUsedTask(executeResult.task)
    }
    if (executeResult.kind === TaskExecuteKind.Active) {
      const active = executeResult.active
      if (
        (active && active.same && runSource === TaskRunSource.FolderOpen) ||
        runSource === TaskRunSource.Reconnect
      ) {
        // ignore, the task is already active, likely from being reconnected or from folder open.
        this._logService.debug(
          "Ignoring task that is already active",
          executeResult.task,
        )
        return executeResult.promise
      }
      if (active && active.same) {
        if (this._taskSystem?.isTaskVisible(executeResult.task)) {
          const message = nls.localize(
            "TaskSystem.activeSame.noBackground",
            "The task '{0}' is already active.",
            executeResult.task.getQualifiedLabel(),
          )
          const lastInstance =
            this._getTaskSystem().getLastInstance(executeResult.task) ??
            executeResult.task
          this._notificationService.prompt(
            Severity.Warning,
            message,
            [
              {
                label: nls.localize("terminateTask", "Terminate Task"),
                run: () => this.terminate(lastInstance),
              },
              {
                label: nls.localize("restartTask", "Restart Task"),
                run: () => this._restart(lastInstance),
              },
            ],
            { sticky: true },
          )
        } else {
          this._taskSystem?.revealTask(executeResult.task)
        }
      } else {
        throw new TaskError(
          Severity.Warning,
          nls.localize(
            "TaskSystem.active",
            "There is already a task running. Terminate it first before executing another task.",
          ),
          TaskErrors.RunningTask,
        )
      }
    }
    this._setRecentlyUsedTask(executeResult.task)
    return executeResult.promise
  }

  private async _restart(task: Task): Promise<void> {
    if (!this._taskSystem) {
      return
    }
    const response = await this._taskSystem.terminate(task)
    if (response.success) {
      try {
        await this.run(task)
      } catch {
        // eat the error, we don't care about it here
      }
    } else {
      this._notificationService.warn(
        nls.localize(
          "TaskSystem.restartFailed",
          "Failed to terminate and restart task {0}",
          Types.isString(task) ? task : task.configurationProperties.name,
        ),
      )
    }
  }

  public async terminate(task: Task): Promise<ITaskTerminateResponse> {
    if (!(await this._trust())) {
      return { success: true, task: undefined }
    }

    if (!this._taskSystem) {
      return { success: true, task: undefined }
    }
    return this._taskSystem.terminate(task)
  }

  private _terminateAll(): Promise<ITaskTerminateResponse[]> {
    if (!this._taskSystem) {
      return Promise.resolve<ITaskTerminateResponse[]>([])
    }
    return this._taskSystem.terminateAll()
  }

  protected _createTerminalTaskSystem(): ITaskSystem {
    return new TerminalTaskSystem(
      this._terminalService,
      this._terminalGroupService,
      this._outputService,
      this._paneCompositeService,
      this._viewsService,
      this._markerService,
      this._modelService,
      this._configurationResolverService,
      this._contextService,
      this._environmentService,
      AbstractTaskService.OutputChannelId,
      this._fileService,
      this._terminalProfileResolverService,
      this._pathService,
      this._viewDescriptorService,
      this._logService,
      this._notificationService,
      this._instantiationService,
      (workspaceFolder: IWorkspaceFolder | undefined) => {
        if (workspaceFolder) {
          return this._getTaskSystemInfo(workspaceFolder.uri.scheme)
        } else if (this._taskSystemInfos.size > 0) {
          const infos = Array.from(this._taskSystemInfos.entries())
          const notFile = infos.filter((info) => info[0] !== Schemas.file)
          if (notFile.length > 0) {
            return notFile[0][1][0]
          }
          return infos[0][1][0]
        } else {
          return undefined
        }
      },
    )
  }

  protected abstract _getTaskSystem(): ITaskSystem

  private _isTaskProviderEnabled(type: string) {
    const definition = TaskDefinitionRegistry.get(type)
    return (
      !definition ||
      !definition.when ||
      this._contextKeyService.contextMatchesRules(definition.when)
    )
  }

  private async _getGroupedTasks(filter?: ITaskFilter): Promise<TaskMap> {
    await this._waitForAllSupportedExecutions
    const type = filter?.type
    const needsRecentTasksMigration = this._needsRecentTasksMigration()
    await this._activateTaskProviders(filter?.type)
    const validTypes: IStringDictionary<boolean> = Object.create(null)
    TaskDefinitionRegistry.all().forEach(
      (definition) => (validTypes[definition.taskType] = true),
    )
    validTypes["shell"] = true
    validTypes["process"] = true
    const contributedTaskSets = await new Promise<ITaskSet[]>((resolve) => {
      const result: ITaskSet[] = []
      let counter: number = 0
      const done = (value: ITaskSet | undefined) => {
        if (value) {
          result.push(value)
        }
        if (--counter === 0) {
          resolve(result)
        }
      }
      const error = (error: any) => {
        try {
          if (error && Types.isString(error.message)) {
            this._log(`Error: ${error.message}\n`)
            this._showOutput()
          } else {
            this._log(
              "Unknown error received while collecting tasks from providers.",
            )
            this._showOutput()
          }
        } finally {
          if (--counter === 0) {
            resolve(result)
          }
        }
      }
      if (
        this._isProvideTasksEnabled() &&
        this.schemaVersion === JsonSchemaVersion.V2_0_0 &&
        this._providers.size > 0
      ) {
        let foundAnyProviders = false
        for (const [handle, provider] of this._providers) {
          const providerType = this._providerTypes.get(handle)
          if (type === undefined || type === providerType) {
            if (providerType && !this._isTaskProviderEnabled(providerType)) {
              continue
            }
            foundAnyProviders = true
            counter++
            raceTimeout(
              provider.provideTasks(validTypes).then((taskSet: ITaskSet) => {
                // Check that the tasks provided are of the correct type
                for (const task of taskSet.tasks) {
                  if (task.type !== this._providerTypes.get(handle)) {
                    this._log(
                      nls.localize(
                        "unexpectedTaskType",
                        'The task provider for "{0}" tasks unexpectedly provided a task of type "{1}".\n',
                        this._providerTypes.get(handle),
                        task.type,
                      ),
                    )
                    if (task.type !== "shell" && task.type !== "process") {
                      this._showOutput()
                    }
                    break
                  }
                }
                return done(taskSet)
              }, error),
              5000,
              () => {
                // onTimeout
                console.error("Timed out getting tasks from ", providerType)
                done(undefined)
              },
            )
          }
        }
        if (!foundAnyProviders) {
          resolve(result)
        }
      } else {
        resolve(result)
      }
    })

    const result: TaskMap = new TaskMap()
    const contributedTasks: TaskMap = new TaskMap()

    for (const set of contributedTaskSets) {
      for (const task of set.tasks) {
        const workspaceFolder = task.getWorkspaceFolder()
        if (workspaceFolder) {
          contributedTasks.add(workspaceFolder, task)
        }
      }
    }

    try {
      const customTasks = await this.getWorkspaceTasks()
      const customTasksKeyValuePairs = Array.from(customTasks)
      const customTasksPromises = customTasksKeyValuePairs.map(
        async ([key, folderTasks]) => {
          const contributed = contributedTasks.get(key)
          if (!folderTasks.set) {
            if (contributed) {
              result.add(key, ...contributed)
            }
            return
          }

          if (
            this._contextService.getWorkbenchState() === WorkbenchState.EMPTY
          ) {
            result.add(key, ...folderTasks.set.tasks)
          } else {
            const configurations = folderTasks.configurations
            const legacyTaskConfigurations = folderTasks.set
              ? this._getLegacyTaskConfigurations(folderTasks.set)
              : undefined
            const customTasksToDelete: Task[] = []
            if (configurations || legacyTaskConfigurations) {
              const unUsedConfigurations: Set<string> = new Set<string>()
              if (configurations) {
                Object.keys(configurations.byIdentifier).forEach((key) =>
                  unUsedConfigurations.add(key),
                )
              }
              for (const task of contributed) {
                if (!ContributedTask.is(task)) {
                  continue
                }
                if (configurations) {
                  const configuringTask =
                    configurations.byIdentifier[task.defines._key]
                  if (configuringTask) {
                    unUsedConfigurations.delete(task.defines._key)
                    result.add(
                      key,
                      TaskConfig.createCustomTask(task, configuringTask),
                    )
                  } else {
                    result.add(key, task)
                  }
                } else if (legacyTaskConfigurations) {
                  const configuringTask =
                    legacyTaskConfigurations[task.defines._key]
                  if (configuringTask) {
                    result.add(
                      key,
                      TaskConfig.createCustomTask(task, configuringTask),
                    )
                    customTasksToDelete.push(configuringTask)
                  } else {
                    result.add(key, task)
                  }
                } else {
                  result.add(key, task)
                }
              }
              if (customTasksToDelete.length > 0) {
                const toDelete = customTasksToDelete.reduce<
                  IStringDictionary<boolean>
                >((map, task) => {
                  map[task._id] = true
                  return map
                }, Object.create(null))
                for (const task of folderTasks.set.tasks) {
                  if (toDelete[task._id]) {
                    continue
                  }
                  result.add(key, task)
                }
              } else {
                result.add(key, ...folderTasks.set.tasks)
              }

              const unUsedConfigurationsAsArray =
                Array.from(unUsedConfigurations)

              const unUsedConfigurationPromises =
                unUsedConfigurationsAsArray.map(async (value) => {
                  const configuringTask = configurations!.byIdentifier[value]
                  if (type && type !== configuringTask.configures.type) {
                    return
                  }

                  let requiredTaskProviderUnavailable: boolean = false

                  for (const [handle, provider] of this._providers) {
                    const providerType = this._providerTypes.get(handle)
                    if (configuringTask.type === providerType) {
                      if (
                        providerType &&
                        !this._isTaskProviderEnabled(providerType)
                      ) {
                        requiredTaskProviderUnavailable = true
                        continue
                      }

                      try {
                        const resolvedTask =
                          await provider.resolveTask(configuringTask)
                        if (
                          resolvedTask &&
                          resolvedTask._id === configuringTask._id
                        ) {
                          result.add(
                            key,
                            TaskConfig.createCustomTask(
                              resolvedTask,
                              configuringTask,
                            ),
                          )
                          return
                        }
                      } catch (error) {
                        // Ignore errors. The task could not be provided by any of the providers.
                      }
                    }
                  }

                  if (requiredTaskProviderUnavailable) {
                    this._log(
                      nls.localize(
                        "TaskService.providerUnavailable",
                        "Warning: {0} tasks are unavailable in the current environment.",
                        configuringTask.configures.type,
                      ),
                    )
                  } else {
                    this._log(
                      nls.localize(
                        "TaskService.noConfiguration",
                        "Error: The {0} task detection didn't contribute a task for the following configuration:\n{1}\nThe task will be ignored.",
                        configuringTask.configures.type,
                        JSON.stringify(
                          configuringTask._source.config.element,
                          undefined,
                          4,
                        ),
                      ),
                    )
                    this._showOutput()
                  }
                })

              await Promise.all(unUsedConfigurationPromises)
            } else {
              result.add(key, ...folderTasks.set.tasks)
              result.add(key, ...contributed)
            }
          }
        },
      )

      await Promise.all(customTasksPromises)
      if (needsRecentTasksMigration) {
        // At this point we have all the tasks and can migrate the recently used tasks.
        await this._migrateRecentTasks(result.all())
      }
      return result
    } catch {
      // If we can't read the tasks.json file provide at least the contributed tasks
      const result: TaskMap = new TaskMap()
      for (const set of contributedTaskSets) {
        for (const task of set.tasks) {
          const folder = task.getWorkspaceFolder()
          if (folder) {
            result.add(folder, task)
          }
        }
      }
      return result
    }
  }

  private _getLegacyTaskConfigurations(
    workspaceTasks: ITaskSet,
  ): IStringDictionary<CustomTask> | undefined {
    let result: IStringDictionary<CustomTask> | undefined
    function getResult(): IStringDictionary<CustomTask> {
      if (result) {
        return result
      }
      result = Object.create(null)
      return result!
    }
    for (const task of workspaceTasks.tasks) {
      if (CustomTask.is(task)) {
        const commandName = task.command && task.command.name
        // This is for backwards compatibility with the 0.1.0 task annotation code
        // if we had a gulp, jake or grunt command a task specification was a annotation
        if (
          commandName === "gulp" ||
          commandName === "grunt" ||
          commandName === "jake"
        ) {
          const identifier = KeyedTaskIdentifier.create({
            type: commandName,
            task: task.configurationProperties.name,
          })
          getResult()[identifier._key] = task
        }
      }
    }
    return result
  }

  public async getWorkspaceTasks(
    runSource: TaskRunSource = TaskRunSource.User,
  ): Promise<Map<string, IWorkspaceFolderTaskResult>> {
    if (!(await this._trust())) {
      return new Map()
    }
    await raceTimeout(this._waitForAllSupportedExecutions, 2000, () => {
      this._logService.warn("Timed out waiting for all supported executions")
    })
    await this._whenTaskSystemReady
    if (this._workspaceTasksPromise) {
      return this._workspaceTasksPromise
    }
    return this._updateWorkspaceTasks(runSource)
  }

  private _updateWorkspaceTasks(
    runSource: TaskRunSource = TaskRunSource.User,
  ): Promise<Map<string, IWorkspaceFolderTaskResult>> {
    this._workspaceTasksPromise = this._computeWorkspaceTasks(runSource)
    return this._workspaceTasksPromise
  }

  private async _getAFolder(): Promise<IWorkspaceFolder> {
    let folder =
      this.workspaceFolders.length > 0 ? this.workspaceFolders[0] : undefined
    if (!folder) {
      const userhome = await this._pathService.userHome()
      folder = new WorkspaceFolder({
        uri: userhome,
        name: resources.basename(userhome),
        index: 0,
      })
    }
    return folder
  }

  protected async _computeWorkspaceTasks(
    runSource: TaskRunSource = TaskRunSource.User,
  ): Promise<Map<string, IWorkspaceFolderTaskResult>> {
    const promises: Promise<IWorkspaceFolderTaskResult | undefined>[] = []
    for (const folder of this.workspaceFolders) {
      promises.push(this._computeWorkspaceFolderTasks(folder, runSource))
    }
    const values = await Promise.all(promises)
    const result = new Map<string, IWorkspaceFolderTaskResult>()
    for (const value of values) {
      if (value) {
        result.set(value.workspaceFolder.uri.toString(), value)
      }
    }

    const folder = await this._getAFolder()
    if (this._contextService.getWorkbenchState() !== WorkbenchState.EMPTY) {
      const workspaceFileTasks = await this._computeWorkspaceFileTasks(
        folder,
        runSource,
      )
      if (
        workspaceFileTasks &&
        this._workspace &&
        this._workspace.configuration
      ) {
        result.set(this._workspace.configuration.toString(), workspaceFileTasks)
      }
    }

    const userTasks = await this._computeUserTasks(folder, runSource)
    if (userTasks) {
      result.set(USER_TASKS_GROUP_KEY, userTasks)
    }
    return result
  }

  private get _jsonTasksSupported(): boolean {
    return (
      ShellExecutionSupportedContext.getValue(this._contextKeyService) ===
        true &&
      ProcessExecutionSupportedContext.getValue(this._contextKeyService) ===
        true
    )
  }

  private async _computeWorkspaceFolderTasks(
    workspaceFolder: IWorkspaceFolder,
    runSource: TaskRunSource = TaskRunSource.User,
  ): Promise<IWorkspaceFolderTaskResult> {
    const workspaceFolderConfiguration =
      this._executionEngine === ExecutionEngine.Process
        ? await this._computeLegacyConfiguration(workspaceFolder)
        : await this._computeConfiguration(workspaceFolder)
    if (
      !workspaceFolderConfiguration ||
      !workspaceFolderConfiguration.config ||
      workspaceFolderConfiguration.hasErrors
    ) {
      return Promise.resolve({
        workspaceFolder,
        set: undefined,
        configurations: undefined,
        hasErrors: workspaceFolderConfiguration
          ? workspaceFolderConfiguration.hasErrors
          : false,
      })
    }
    await ProblemMatcherRegistry.onReady()
    const taskSystemInfo: ITaskSystemInfo | undefined = this._getTaskSystemInfo(
      workspaceFolder.uri.scheme,
    )
    const problemReporter = new ProblemReporter(this._outputChannel)
    const parseResult = TaskConfig.parse(
      workspaceFolder,
      undefined,
      taskSystemInfo ? taskSystemInfo.platform : Platform.platform,
      workspaceFolderConfiguration.config,
      problemReporter,
      TaskConfig.TaskConfigSource.TasksJson,
      this._contextKeyService,
    )
    let hasErrors = false
    if (
      !parseResult.validationStatus.isOK() &&
      parseResult.validationStatus.state !== ValidationState.Info
    ) {
      hasErrors = true
      this._showOutput(runSource)
    }
    if (problemReporter.status.isFatal()) {
      problemReporter.fatal(
        nls.localize(
          "TaskSystem.configurationErrors",
          "Error: the provided task configuration has validation errors and can't not be used. Please correct the errors first.",
        ),
      )
      return {
        workspaceFolder,
        set: undefined,
        configurations: undefined,
        hasErrors,
      }
    }
    let customizedTasks:
      | { byIdentifier: IStringDictionary<ConfiguringTask> }
      | undefined
    if (parseResult.configured && parseResult.configured.length > 0) {
      customizedTasks = {
        byIdentifier: Object.create(null),
      }
      for (const task of parseResult.configured) {
        customizedTasks.byIdentifier[task.configures._key] = task
      }
    }
    if (!this._jsonTasksSupported && parseResult.custom.length > 0) {
      console.warn("Custom workspace tasks are not supported.")
    }
    return {
      workspaceFolder,
      set: { tasks: this._jsonTasksSupported ? parseResult.custom : [] },
      configurations: customizedTasks,
      hasErrors,
    }
  }

  private _testParseExternalConfig(
    config: TaskConfig.IExternalTaskRunnerConfiguration | undefined,
    location: string,
  ): {
    config: TaskConfig.IExternalTaskRunnerConfiguration | undefined
    hasParseErrors: boolean
  } {
    if (!config) {
      return { config: undefined, hasParseErrors: false }
    }
    const parseErrors: string[] = (config as any).$parseErrors
    if (parseErrors) {
      let isAffected = false
      for (const parseError of parseErrors) {
        if (/tasks\.json$/.test(parseError)) {
          isAffected = true
          break
        }
      }
      if (isAffected) {
        this._log(
          nls.localize(
            {
              key: "TaskSystem.invalidTaskJsonOther",
              comment: [
                "Message notifies of an error in one of several places there is tasks related json, not necessarily in a file named tasks.json",
              ],
            },
            "Error: The content of the tasks json in {0} has syntax errors. Please correct them before executing a task.",
            location,
          ),
        )
        this._showOutput()
        return { config, hasParseErrors: true }
      }
    }
    return { config, hasParseErrors: false }
  }

  private _log(value: string, verbose?: boolean): void {
    if (
      !verbose ||
      this._configurationService.getValue(TaskSettingId.VerboseLogging)
    ) {
      this._outputChannel.append(value + "\n")
    }
  }

  private async _computeWorkspaceFileTasks(
    workspaceFolder: IWorkspaceFolder,
    runSource: TaskRunSource = TaskRunSource.User,
  ): Promise<IWorkspaceFolderTaskResult> {
    if (this._executionEngine === ExecutionEngine.Process) {
      return this._emptyWorkspaceTaskResults(workspaceFolder)
    }
    const workspaceFileConfig = this._getConfiguration(
      workspaceFolder,
      TaskSourceKind.WorkspaceFile,
    )
    const configuration = this._testParseExternalConfig(
      workspaceFileConfig.config,
      nls.localize("TasksSystem.locationWorkspaceConfig", "workspace file"),
    )
    const customizedTasks: {
      byIdentifier: IStringDictionary<ConfiguringTask>
    } = {
      byIdentifier: Object.create(null),
    }

    const custom: CustomTask[] = []
    await this._computeTasksForSingleConfig(
      workspaceFolder,
      configuration.config,
      runSource,
      custom,
      customizedTasks.byIdentifier,
      TaskConfig.TaskConfigSource.WorkspaceFile,
    )
    const engine = configuration.config
      ? TaskConfig.ExecutionEngine.from(configuration.config)
      : ExecutionEngine.Terminal
    if (engine === ExecutionEngine.Process) {
      this._notificationService.warn(
        nls.localize(
          "TaskSystem.versionWorkspaceFile",
          "Only tasks version 2.0.0 permitted in workspace configuration files.",
        ),
      )
      return this._emptyWorkspaceTaskResults(workspaceFolder)
    }
    return {
      workspaceFolder,
      set: { tasks: custom },
      configurations: customizedTasks,
      hasErrors: configuration.hasParseErrors,
    }
  }

  private async _computeUserTasks(
    workspaceFolder: IWorkspaceFolder,
    runSource: TaskRunSource = TaskRunSource.User,
  ): Promise<IWorkspaceFolderTaskResult> {
    if (this._executionEngine === ExecutionEngine.Process) {
      return this._emptyWorkspaceTaskResults(workspaceFolder)
    }
    const userTasksConfig = this._getConfiguration(
      workspaceFolder,
      TaskSourceKind.User,
    )
    const configuration = this._testParseExternalConfig(
      userTasksConfig.config,
      nls.localize("TasksSystem.locationUserConfig", "user settings"),
    )
    const customizedTasks: {
      byIdentifier: IStringDictionary<ConfiguringTask>
    } = {
      byIdentifier: Object.create(null),
    }

    const custom: CustomTask[] = []
    await this._computeTasksForSingleConfig(
      workspaceFolder,
      configuration.config,
      runSource,
      custom,
      customizedTasks.byIdentifier,
      TaskConfig.TaskConfigSource.User,
    )
    const engine = configuration.config
      ? TaskConfig.ExecutionEngine.from(configuration.config)
      : ExecutionEngine.Terminal
    if (engine === ExecutionEngine.Process) {
      this._notificationService.warn(
        nls.localize(
          "TaskSystem.versionSettings",
          "Only tasks version 2.0.0 permitted in user settings.",
        ),
      )
      return this._emptyWorkspaceTaskResults(workspaceFolder)
    }
    return {
      workspaceFolder,
      set: { tasks: custom },
      configurations: customizedTasks,
      hasErrors: configuration.hasParseErrors,
    }
  }

  private _emptyWorkspaceTaskResults(
    workspaceFolder: IWorkspaceFolder,
  ): IWorkspaceFolderTaskResult {
    return {
      workspaceFolder,
      set: undefined,
      configurations: undefined,
      hasErrors: false,
    }
  }

  private async _computeTasksForSingleConfig(
    workspaceFolder: IWorkspaceFolder,
    config: TaskConfig.IExternalTaskRunnerConfiguration | undefined,
    runSource: TaskRunSource,
    custom: CustomTask[],
    customized: IStringDictionary<ConfiguringTask>,
    source: TaskConfig.TaskConfigSource,
    isRecentTask: boolean = false,
  ): Promise<boolean> {
    if (!config) {
      return false
    } else if (!workspaceFolder) {
      this._logService.trace(
        "TaskService.computeTasksForSingleConfig: no workspace folder for worskspace",
        this._workspace?.id,
      )
      return false
    }
    const taskSystemInfo: ITaskSystemInfo | undefined = this._getTaskSystemInfo(
      workspaceFolder.uri.scheme,
    )
    const problemReporter = new ProblemReporter(this._outputChannel)
    const parseResult = TaskConfig.parse(
      workspaceFolder,
      this._workspace,
      taskSystemInfo ? taskSystemInfo.platform : Platform.platform,
      config,
      problemReporter,
      source,
      this._contextKeyService,
      isRecentTask,
    )
    let hasErrors = false
    if (
      !parseResult.validationStatus.isOK() &&
      parseResult.validationStatus.state !== ValidationState.Info
    ) {
      this._showOutput(runSource)
      hasErrors = true
    }
    if (problemReporter.status.isFatal()) {
      problemReporter.fatal(
        nls.localize(
          "TaskSystem.configurationErrors",
          "Error: the provided task configuration has validation errors and can't not be used. Please correct the errors first.",
        ),
      )
      return hasErrors
    }
    if (parseResult.configured && parseResult.configured.length > 0) {
      for (const task of parseResult.configured) {
        customized[task.configures._key] = task
      }
    }
    if (!this._jsonTasksSupported && parseResult.custom.length > 0) {
      console.warn("Custom workspace tasks are not supported.")
    } else {
      for (const task of parseResult.custom) {
        custom.push(task)
      }
    }
    return hasErrors
  }

  private _computeConfiguration(
    workspaceFolder: IWorkspaceFolder,
  ): Promise<IWorkspaceFolderConfigurationResult> {
    const { config, hasParseErrors } = this._getConfiguration(workspaceFolder)
    return Promise.resolve<IWorkspaceFolderConfigurationResult>({
      workspaceFolder,
      config,
      hasErrors: hasParseErrors,
    })
  }

  protected abstract _computeLegacyConfiguration(
    workspaceFolder: IWorkspaceFolder,
  ): Promise<IWorkspaceFolderConfigurationResult>

  private _computeWorkspaceFolderSetup(): [
    IWorkspaceFolder[],
    IWorkspaceFolder[],
    ExecutionEngine,
    JsonSchemaVersion,
    IWorkspace | undefined,
  ] {
    const workspaceFolders: IWorkspaceFolder[] = []
    const ignoredWorkspaceFolders: IWorkspaceFolder[] = []
    let executionEngine = ExecutionEngine.Terminal
    let schemaVersion = JsonSchemaVersion.V2_0_0
    let workspace: IWorkspace | undefined
    if (this._contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
      const workspaceFolder: IWorkspaceFolder =
        this._contextService.getWorkspace().folders[0]
      workspaceFolders.push(workspaceFolder)
      executionEngine = this._computeExecutionEngine(workspaceFolder)
      const telemetryData: { [key: string]: any } = {
        executionEngineVersion: executionEngine,
      }
      /* __GDPR__
				"taskService.engineVersion" : {
					"owner": "alexr00",
					"comment": "The engine version of tasks. Used to determine if a user is using a deprecated version.",
					"executionEngineVersion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The engine version of tasks." }
				}
			*/
      this._telemetryService.publicLog(
        "taskService.engineVersion",
        telemetryData,
      )
      schemaVersion = this._computeJsonSchemaVersion(workspaceFolder)
    } else if (
      this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE
    ) {
      workspace = this._contextService.getWorkspace()
      for (const workspaceFolder of this._contextService.getWorkspace()
        .folders) {
        if (schemaVersion === this._computeJsonSchemaVersion(workspaceFolder)) {
          workspaceFolders.push(workspaceFolder)
        } else {
          ignoredWorkspaceFolders.push(workspaceFolder)
          this._log(
            nls.localize(
              "taskService.ignoreingFolder",
              "Ignoring task configurations for workspace folder {0}. Multi folder workspace task support requires that all folders use task version 2.0.0",
              workspaceFolder.uri.fsPath,
            ),
          )
        }
      }
    }
    return [
      workspaceFolders,
      ignoredWorkspaceFolders,
      executionEngine,
      schemaVersion,
      workspace,
    ]
  }

  private _computeExecutionEngine(
    workspaceFolder: IWorkspaceFolder,
  ): ExecutionEngine {
    const { config } = this._getConfiguration(workspaceFolder)
    if (!config) {
      return ExecutionEngine._default
    }
    return TaskConfig.ExecutionEngine.from(config)
  }

  private _computeJsonSchemaVersion(
    workspaceFolder: IWorkspaceFolder,
  ): JsonSchemaVersion {
    const { config } = this._getConfiguration(workspaceFolder)
    if (!config) {
      return JsonSchemaVersion.V2_0_0
    }
    return TaskConfig.JsonSchemaVersion.from(config)
  }

  protected _getConfiguration(
    workspaceFolder: IWorkspaceFolder,
    source?: string,
  ): {
    config: TaskConfig.IExternalTaskRunnerConfiguration | undefined
    hasParseErrors: boolean
  } {
    let result
    if (
      source !== TaskSourceKind.User &&
      this._contextService.getWorkbenchState() === WorkbenchState.EMPTY
    ) {
      result = undefined
    } else {
      const wholeConfig =
        this._configurationService.inspect<TaskConfig.IExternalTaskRunnerConfiguration>(
          "tasks",
          { resource: workspaceFolder.uri },
        )
      switch (source) {
        case TaskSourceKind.User: {
          if (wholeConfig.userValue !== wholeConfig.workspaceFolderValue) {
            result = Objects.deepClone(wholeConfig.userValue)
          }
          break
        }
        case TaskSourceKind.Workspace:
          result = Objects.deepClone(wholeConfig.workspaceFolderValue)
          break
        case TaskSourceKind.WorkspaceFile: {
          if (
            this._contextService.getWorkbenchState() ===
              WorkbenchState.WORKSPACE &&
            wholeConfig.workspaceFolderValue !== wholeConfig.workspaceValue
          ) {
            result = Objects.deepClone(wholeConfig.workspaceValue)
          }
          break
        }
        default:
          result = Objects.deepClone(wholeConfig.workspaceFolderValue)
      }
    }
    if (!result) {
      return { config: undefined, hasParseErrors: false }
    }
    const parseErrors: string[] = (result as any).$parseErrors
    if (parseErrors) {
      let isAffected = false
      for (const parseError of parseErrors) {
        if (/tasks\.json$/.test(parseError)) {
          isAffected = true
          break
        }
      }
      if (isAffected) {
        this._log(
          nls.localize(
            "TaskSystem.invalidTaskJson",
            "Error: The content of the tasks.json file has syntax errors. Please correct them before executing a task.",
          ),
        )
        this._showOutput()
        return { config: undefined, hasParseErrors: true }
      }
    }
    return { config: result, hasParseErrors: false }
  }

  public inTerminal(): boolean {
    if (this._taskSystem) {
      return this._taskSystem instanceof TerminalTaskSystem
    }
    return this._executionEngine === ExecutionEngine.Terminal
  }

  public configureAction(): Action {
    const thisCapture: AbstractTaskService = this
    return new (class extends Action {
      constructor() {
        super(
          ConfigureTaskAction.ID,
          ConfigureTaskAction.TEXT.value,
          undefined,
          true,
          () => {
            thisCapture._runConfigureTasks()
            return Promise.resolve(undefined)
          },
        )
      }
    })()
  }

  private _handleError(err: any): void {
    let showOutput = true
    if (err instanceof TaskError) {
      const buildError = err
      const needsConfig =
        buildError.code === TaskErrors.NotConfigured ||
        buildError.code === TaskErrors.NoBuildTask ||
        buildError.code === TaskErrors.NoTestTask
      const needsTerminate = buildError.code === TaskErrors.RunningTask
      if (needsConfig || needsTerminate) {
        this._notificationService.prompt(
          buildError.severity,
          buildError.message,
          [
            {
              label: needsConfig
                ? ConfigureTaskAction.TEXT.value
                : nls.localize("TerminateAction.label", "Terminate Task"),
              run: () => {
                if (needsConfig) {
                  this._runConfigureTasks()
                } else {
                  this._runTerminateCommand()
                }
              },
            },
          ],
        )
      } else {
        this._notificationService.notify({
          severity: buildError.severity,
          message: buildError.message,
        })
      }
    } else if (err instanceof Error) {
      const error = err
      this._notificationService.error(error.message)
      showOutput = false
    } else if (Types.isString(err)) {
      this._notificationService.error(<string>err)
    } else {
      this._notificationService.error(
        nls.localize(
          "TaskSystem.unknownError",
          "An error has occurred while running a task. See task log for details.",
        ),
      )
    }
    if (showOutput) {
      this._showOutput()
    }
  }

  private _showDetail(): boolean {
    return this._configurationService.getValue<boolean>(QUICKOPEN_DETAIL_CONFIG)
  }

  private async _createTaskQuickPickEntries(
    tasks: Task[],
    group: boolean = false,
    sort: boolean = false,
    selectedEntry?: ITaskQuickPickEntry,
    includeRecents: boolean = true,
  ): Promise<ITaskQuickPickEntry[]> {
    let encounteredTasks: { [key: string]: ITaskQuickPickEntry[] } = {}
    if (tasks === undefined || tasks === null || tasks.length === 0) {
      return []
    }
    const TaskQuickPickEntry = (task: Task): ITaskQuickPickEntry => {
      const newEntry = {
        label: task._label,
        description: this.getTaskDescription(task),
        task,
        detail: this._showDetail()
          ? task.configurationProperties.detail
          : undefined,
      }
      if (encounteredTasks[task._id]) {
        if (encounteredTasks[task._id].length === 1) {
          encounteredTasks[task._id][0].label += " (1)"
        }
        newEntry.label =
          newEntry.label +
          " (" +
          (encounteredTasks[task._id].length + 1).toString() +
          ")"
      } else {
        encounteredTasks[task._id] = []
      }
      encounteredTasks[task._id].push(newEntry)
      return newEntry
    }
    function fillEntries(
      entries: QuickPickInput<ITaskQuickPickEntry>[],
      tasks: Task[],
      groupLabel: string,
    ): void {
      if (tasks.length) {
        entries.push({ type: "separator", label: groupLabel })
      }
      for (const task of tasks) {
        const entry: ITaskQuickPickEntry = TaskQuickPickEntry(task)
        entry.buttons = [
          {
            iconClass: ThemeIcon.asClassName(configureTaskIcon),
            tooltip: nls.localize("configureTask", "Configure Task"),
          },
        ]
        if (selectedEntry && task === selectedEntry.task) {
          entries.unshift(selectedEntry)
        } else {
          entries.push(entry)
        }
      }
    }
    let entries: ITaskQuickPickEntry[]
    if (group) {
      entries = []
      if (tasks.length === 1) {
        entries.push(TaskQuickPickEntry(tasks[0]))
      } else {
        const recentlyUsedTasks = await this.getSavedTasks("historical")
        const recent: Task[] = []
        const recentSet: Set<string> = new Set()
        let configured: Task[] = []
        let detected: Task[] = []
        const taskMap: IStringDictionary<Task> = Object.create(null)
        tasks.forEach((task) => {
          const key = task.getCommonTaskId()
          if (key) {
            taskMap[key] = task
          }
        })
        recentlyUsedTasks.reverse().forEach((recentTask) => {
          const key = recentTask.getCommonTaskId()
          if (key) {
            recentSet.add(key)
            const task = taskMap[key]
            if (task) {
              recent.push(task)
            }
          }
        })
        for (const task of tasks) {
          const key = task.getCommonTaskId()
          if (!key || !recentSet.has(key)) {
            if (
              task._source.kind === TaskSourceKind.Workspace ||
              task._source.kind === TaskSourceKind.User
            ) {
              configured.push(task)
            } else {
              detected.push(task)
            }
          }
        }
        const sorter = this.createSorter()
        if (includeRecents) {
          fillEntries(
            entries,
            recent,
            nls.localize("recentlyUsed", "recently used tasks"),
          )
        }
        configured = configured.sort((a, b) => sorter.compare(a, b))
        fillEntries(
          entries,
          configured,
          nls.localize("configured", "configured tasks"),
        )
        detected = detected.sort((a, b) => sorter.compare(a, b))
        fillEntries(
          entries,
          detected,
          nls.localize("detected", "detected tasks"),
        )
      }
    } else {
      if (sort) {
        const sorter = this.createSorter()
        tasks = tasks.sort((a, b) => sorter.compare(a, b))
      }
      entries = tasks.map<ITaskQuickPickEntry>((task) =>
        TaskQuickPickEntry(task),
      )
    }
    encounteredTasks = {}
    return entries
  }
  private async _showTwoLevelQuickPick(
    placeHolder: string,
    defaultEntry?: ITaskQuickPickEntry,
    type?: string,
    name?: string,
  ) {
    return this._instantiationService
      .createInstance(TaskQuickPick)
      .show(placeHolder, defaultEntry, type, name)
  }

  private async _showQuickPick(
    tasks: Promise<Task[]> | Task[],
    placeHolder: string,
    defaultEntry?: ITaskQuickPickEntry,
    group: boolean = false,
    sort: boolean = false,
    selectedEntry?: ITaskQuickPickEntry,
    additionalEntries?: ITaskQuickPickEntry[],
    name?: string,
  ): Promise<ITaskQuickPickEntry | undefined | null> {
    const resolvedTasks = await tasks
    const entries: (ITaskQuickPickEntry | IQuickPickSeparator)[] | undefined =
      await raceTimeout(
        this._createTaskQuickPickEntries(
          resolvedTasks,
          group,
          sort,
          selectedEntry,
        ),
        200,
        () => undefined,
      )
    if (!entries) {
      return undefined
    }
    if (
      entries.length === 1 &&
      this._configurationService.getValue<boolean>(QUICKOPEN_SKIP_CONFIG)
    ) {
      return <ITaskQuickPickEntry>entries[0]
    } else if (entries.length === 0 && defaultEntry) {
      entries.push(defaultEntry)
    } else if (
      entries.length > 1 &&
      additionalEntries &&
      additionalEntries.length > 0
    ) {
      entries.push({ type: "separator", label: "" })
      entries.push(additionalEntries[0])
    }

    const picker: IQuickPick<ITaskQuickPickEntry> =
      this._quickInputService.createQuickPick()
    picker.placeholder = placeHolder
    picker.matchOnDescription = true
    if (name) {
      picker.value = name
    }
    picker.onDidTriggerItemButton((context) => {
      const task = context.item.task
      this._quickInputService.cancel()
      if (ContributedTask.is(task)) {
        this.customize(task, undefined, true)
      } else if (CustomTask.is(task)) {
        this.openConfig(task)
      }
    })
    picker.items = entries
    picker.show()

    return new Promise<ITaskQuickPickEntry | undefined | null>((resolve) => {
      this._register(
        picker.onDidAccept(async () => {
          const selectedEntry = picker.selectedItems
            ? picker.selectedItems[0]
            : undefined
          picker.dispose()
          if (!selectedEntry) {
            resolve(undefined)
          }
          resolve(selectedEntry)
        }),
      )
    })
  }

  private _needsRecentTasksMigration(): boolean {
    return (
      this.getRecentlyUsedTasksV1().size > 0 &&
      this._getTasksFromStorage("historical").size === 0
    )
  }

  private async _migrateRecentTasks(tasks: Task[]) {
    if (!this._needsRecentTasksMigration()) {
      return
    }
    const recentlyUsedTasks = this.getRecentlyUsedTasksV1()
    const taskMap: IStringDictionary<Task> = Object.create(null)
    tasks.forEach((task) => {
      const key = task.getKey()
      if (key) {
        taskMap[key] = task
      }
    })
    const reversed = [...recentlyUsedTasks.keys()].reverse()
    for (const key in reversed) {
      const task = taskMap[key]
      if (task) {
        await this._setRecentlyUsedTask(task)
      }
    }
    this._storageService.remove(
      AbstractTaskService.RecentlyUsedTasks_Key,
      StorageScope.WORKSPACE,
    )
  }

  private _showIgnoredFoldersMessage(): Promise<void> {
    if (this.ignoredWorkspaceFolders.length === 0 || !this.showIgnoreMessage) {
      return Promise.resolve(undefined)
    }

    this._notificationService.prompt(
      Severity.Info,
      nls.localize(
        "TaskService.ignoredFolder",
        "The following workspace folders are ignored since they use task version 0.1.0: {0}",
        this.ignoredWorkspaceFolders.map((f) => f.name).join(", "),
      ),
      [
        {
          label: nls.localize("TaskService.notAgain", "Don't Show Again"),
          isSecondary: true,
          run: () => {
            this._storageService.store(
              AbstractTaskService.IgnoreTask010DonotShowAgain_key,
              true,
              StorageScope.WORKSPACE,
              StorageTarget.MACHINE,
            )
            this._showIgnoreMessage = false
          },
        },
      ],
    )

    return Promise.resolve(undefined)
  }

  private async _trust(): Promise<boolean> {
    if (ServerlessWebContext && !TaskExecutionSupportedContext) {
      return false
    }
    await this._workspaceTrustManagementService.workspaceTrustInitialized
    if (!this._workspaceTrustManagementService.isWorkspaceTrusted()) {
      return (
        (await this._workspaceTrustRequestService.requestWorkspaceTrust({
          message: nls.localize(
            "TaskService.requestTrust",
            "Listing and running tasks requires that some of the files in this workspace be executed as code.",
          ),
        })) === true
      )
    }
    return true
  }

  private async _runTaskCommand(
    filter?: string | ITaskIdentifier,
  ): Promise<void> {
    if (!this._tasksReconnected) {
      return
    }
    if (!filter) {
      return this._doRunTaskCommand()
    }
    const type = typeof filter === "string" ? undefined : filter.type
    const taskName = typeof filter === "string" ? filter : filter.task
    const grouped = await this._getGroupedTasks({ type })
    const identifier = this._getTaskIdentifier(filter)
    const tasks = grouped.all()
    const resolver = this._createResolver(grouped)
    const folderURIs: (URI | string)[] = this._contextService
      .getWorkspace()
      .folders.map((folder) => folder.uri)
    if (this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
      folderURIs.push(this._contextService.getWorkspace().configuration!)
    }
    folderURIs.push(USER_TASKS_GROUP_KEY)
    if (identifier) {
      for (const uri of folderURIs) {
        const task = await resolver.resolve(uri, identifier)
        if (task) {
          this.run(task)
          return
        }
      }
    }
    const exactMatchTask = !taskName
      ? undefined
      : tasks.find(
          (t) =>
            t.configurationProperties.identifier === taskName ||
            t.getDefinition(true)?.configurationProperties?.identifier ===
              taskName,
        )
    if (!exactMatchTask) {
      return this._doRunTaskCommand(tasks, type, taskName)
    }
    for (const uri of folderURIs) {
      const task = await resolver.resolve(uri, taskName)
      if (task) {
        await this.run(task, { attachProblemMatcher: true }, TaskRunSource.User)
        return
      }
    }
  }

  private _tasksAndGroupedTasks(filter?: ITaskFilter): {
    tasks: Promise<Task[]>
    grouped: Promise<TaskMap>
  } {
    if (!this._versionAndEngineCompatible(filter)) {
      return {
        tasks: Promise.resolve<Task[]>([]),
        grouped: Promise.resolve(new TaskMap()),
      }
    }
    const grouped = this._getGroupedTasks(filter)
    const tasks = grouped.then((map) => {
      if (!filter || !filter.type) {
        return map.all()
      }
      const result: Task[] = []
      map.forEach((tasks) => {
        for (const task of tasks) {
          if (ContributedTask.is(task) && task.defines.type === filter.type) {
            result.push(task)
          } else if (CustomTask.is(task)) {
            if (task.type === filter.type) {
              result.push(task)
            } else {
              const customizes = task.customizes()
              if (customizes && customizes.type === filter.type) {
                result.push(task)
              }
            }
          }
        }
      })
      return result
    })
    return { tasks, grouped }
  }

  private _doRunTaskCommand(
    tasks?: Task[],
    type?: string,
    name?: string,
  ): void {
    const pickThen = (task: Task | undefined | null) => {
      if (task === undefined) {
        return
      }
      if (task === null) {
        this._runConfigureTasks()
      } else {
        this.run(task, { attachProblemMatcher: true }, TaskRunSource.User).then(
          undefined,
          (reason) => {
            // eat the error, it has already been surfaced to the user and we don't care about it here
          },
        )
      }
    }

    const placeholder = nls.localize(
      "TaskService.pickRunTask",
      "Select the task to run",
    )

    this._showIgnoredFoldersMessage().then(() => {
      if (this._configurationService.getValue(USE_SLOW_PICKER)) {
        let taskResult:
          | { tasks: Promise<Task[]>; grouped: Promise<TaskMap> }
          | undefined = undefined
        if (!tasks) {
          taskResult = this._tasksAndGroupedTasks()
        }
        this._showQuickPick(
          tasks ? tasks : taskResult!.tasks,
          placeholder,
          {
            label:
              "$(plus) " +
              nls.localize("TaskService.noEntryToRun", "Configure a Task"),
            task: null,
          },
          true,
          undefined,
          undefined,
          undefined,
          name,
        ).then((entry) => {
          return pickThen(entry ? entry.task : undefined)
        })
      } else {
        this._showTwoLevelQuickPick(
          placeholder,
          {
            label:
              "$(plus) " +
              nls.localize("TaskService.noEntryToRun", "Configure a Task"),
            task: null,
          },
          type,
          name,
        ).then(pickThen)
      }
    })
  }

  private _reRunTaskCommand(): void {
    ProblemMatcherRegistry.onReady().then(() => {
      return this._editorService
        .saveAll({ reason: SaveReason.AUTO })
        .then(() => {
          // make sure all dirty editors are saved
          const executeResult = this._getTaskSystem().rerun()
          if (executeResult) {
            return this._handleExecuteResult(executeResult)
          } else {
            if (!this._taskRunningState.get()) {
              // No task running, prompt to ask which to run
              this._doRunTaskCommand()
            }
            return Promise.resolve(undefined)
          }
        })
    })
  }

  /**
   *
   * @param tasks - The tasks which need to be filtered
   * @param tasksInList - This tells splitPerGroupType to filter out globbed tasks (into defaults)
   * @returns
   */
  private _getDefaultTasks(
    tasks: Task[],
    taskGlobsInList: boolean = false,
  ): Task[] {
    const defaults: Task[] = []
    for (const task of tasks.filter((t) => !!t.configurationProperties.group)) {
      // At this point (assuming taskGlobsInList is true) there are tasks with matching globs, so only put those in defaults
      if (
        taskGlobsInList &&
        typeof (task.configurationProperties.group as TaskGroup).isDefault ===
          "string"
      ) {
        defaults.push(task)
      } else if (
        !taskGlobsInList &&
        (task.configurationProperties.group as TaskGroup).isDefault === true
      ) {
        defaults.push(task)
      }
    }
    return defaults
  }

  private _runTaskGroupCommand(
    taskGroup: TaskGroup,
    strings: {
      fetching: string
      select: string
      notFoundConfigure: string
    },
    configure: () => void,
    legacyCommand: () => void,
  ): void {
    if (this.schemaVersion === JsonSchemaVersion.V0_1_0) {
      legacyCommand()
      return
    }
    const options: IProgressOptions = {
      location: ProgressLocation.Window,
      title: strings.fetching,
    }
    const promise = (async () => {
      async function runSingleTask(
        task: Task | undefined,
        problemMatcherOptions: IProblemMatcherRunOptions | undefined,
        that: AbstractTaskService,
      ) {
        that
          .run(task, problemMatcherOptions, TaskRunSource.User)
          .then(undefined, (reason) => {
            // eat the error, it has already been surfaced to the user and we don't care about it here
          })
      }
      const chooseAndRunTask = (tasks: Task[]) => {
        this._showIgnoredFoldersMessage().then(() => {
          this._showQuickPick(
            tasks,
            strings.select,
            {
              label: strings.notFoundConfigure,
              task: null,
            },
            true,
          ).then((entry) => {
            const task: Task | undefined | null = entry ? entry.task : undefined
            if (task === undefined) {
              return
            }
            if (task === null) {
              configure.apply(this)
              return
            }
            runSingleTask(task, { attachProblemMatcher: true }, this)
          })
        })
      }
      let groupTasks: (Task | ConfiguringTask)[] = []
      const { globGroupTasks, globTasksDetected } = await this._getGlobTasks(
        taskGroup._id,
      )
      groupTasks = [...globGroupTasks]
      if (!globTasksDetected && groupTasks.length === 0) {
        groupTasks = await this._findWorkspaceTasksInGroup(taskGroup, true)
      }

      const handleMultipleTasks = (areGlobTasks: boolean) => {
        return this._getTasksForGroup(taskGroup).then((tasks) => {
          if (tasks.length > 0) {
            // If we're dealing with tasks that were chosen because of a glob match,
            // then put globs in the defaults and everything else in none
            const defaults = this._getDefaultTasks(tasks, areGlobTasks)
            if (defaults.length === 1) {
              runSingleTask(defaults[0], undefined, this)
              return
            } else if (defaults.length > 0) {
              tasks = defaults
            }
          }

          // At this this point there are multiple tasks.
          chooseAndRunTask(tasks)
        })
      }

      const resolveTaskAndRun = (taskGroupTask: Task | ConfiguringTask) => {
        if (ConfiguringTask.is(taskGroupTask)) {
          this.tryResolveTask(taskGroupTask).then((resolvedTask) => {
            runSingleTask(resolvedTask, undefined, this)
          })
        } else {
          runSingleTask(taskGroupTask, undefined, this)
        }
      }

      // A single default glob task was returned, just run it directly
      if (groupTasks.length === 1) {
        return resolveTaskAndRun(groupTasks[0])
      }

      // If there's multiple globs that match we want to show the quick picker for those tasks
      // We will need to call splitPerGroupType putting globs in defaults and the remaining tasks in none.
      // We don't need to carry on after here
      if (globTasksDetected && groupTasks.length > 1) {
        return handleMultipleTasks(true)
      }

      // If no globs are found or matched fallback to checking for default tasks of the task group
      if (!groupTasks.length) {
        groupTasks = await this._findWorkspaceTasksInGroup(taskGroup, true)
      }

      if (groupTasks.length === 1) {
        // A single default task was returned, just run it directly
        return resolveTaskAndRun(groupTasks[0])
      }
      // Multiple default tasks returned, show the quickPicker
      return handleMultipleTasks(false)
    })()
    this._progressService.withProgress(options, () => promise)
  }

  private async _getGlobTasks(taskGroupId: string): Promise<{
    globGroupTasks: (Task | ConfiguringTask)[]
    globTasksDetected: boolean
  }> {
    let globTasksDetected = false
    // First check for globs before checking for the default tasks of the task group
    const absoluteURI = EditorResourceAccessor.getOriginalUri(
      this._editorService.activeEditor,
    )
    if (absoluteURI) {
      const workspaceFolder =
        this._contextService.getWorkspaceFolder(absoluteURI)
      if (workspaceFolder) {
        const configuredTasks =
          this._getConfiguration(workspaceFolder)?.config?.tasks
        if (configuredTasks) {
          globTasksDetected =
            configuredTasks.filter(
              (task) =>
                task.group &&
                typeof task.group !== "string" &&
                typeof task.group.isDefault === "string",
            ).length > 0
          // This will activate extensions, so only do so if necessary #185960
          if (globTasksDetected) {
            // Fallback to absolute path of the file if it is not in a workspace or relative path cannot be found
            const relativePath = workspaceFolder?.uri
              ? (resources.relativePath(workspaceFolder.uri, absoluteURI) ??
                absoluteURI.path)
              : absoluteURI.path

            const globGroupTasks = await this._findWorkspaceTasks((task) => {
              const currentTaskGroup = task.configurationProperties.group
              if (
                currentTaskGroup &&
                typeof currentTaskGroup !== "string" &&
                typeof currentTaskGroup.isDefault === "string"
              ) {
                return (
                  currentTaskGroup._id === taskGroupId &&
                  glob.match(currentTaskGroup.isDefault, relativePath)
                )
              }

              globTasksDetected = false
              return false
            })
            return { globGroupTasks, globTasksDetected }
          }
        }
      }
    }
    return { globGroupTasks: [], globTasksDetected }
  }

  private _runBuildCommand(): void {
    if (!this._tasksReconnected) {
      return
    }
    return this._runTaskGroupCommand(
      TaskGroup.Build,
      {
        fetching: nls.localize(
          "TaskService.fetchingBuildTasks",
          "Fetching build tasks...",
        ),
        select: nls.localize(
          "TaskService.pickBuildTask",
          "Select the build task to run",
        ),
        notFoundConfigure: nls.localize(
          "TaskService.noBuildTask",
          "No build task to run found. Configure Build Task...",
        ),
      },
      this._runConfigureDefaultBuildTask,
      this._build,
    )
  }

  private _runTestCommand(): void {
    return this._runTaskGroupCommand(
      TaskGroup.Test,
      {
        fetching: nls.localize(
          "TaskService.fetchingTestTasks",
          "Fetching test tasks...",
        ),
        select: nls.localize(
          "TaskService.pickTestTask",
          "Select the test task to run",
        ),
        notFoundConfigure: nls.localize(
          "TaskService.noTestTaskTerminal",
          "No test task to run found. Configure Tasks...",
        ),
      },
      this._runConfigureDefaultTestTask,
      this._runTest,
    )
  }

  private _runTerminateCommand(arg?: any): void {
    if (arg === "terminateAll") {
      this._terminateAll()
      return
    }
    const runQuickPick = (promise?: Promise<Task[]>) => {
      this._showQuickPick(
        promise || this.getActiveTasks(),
        nls.localize(
          "TaskService.taskToTerminate",
          "Select a task to terminate",
        ),
        {
          label: nls.localize(
            "TaskService.noTaskRunning",
            "No task is currently running",
          ),
          task: undefined,
        },
        false,
        true,
        undefined,
        [
          {
            label: nls.localize(
              "TaskService.terminateAllRunningTasks",
              "All Running Tasks",
            ),
            id: "terminateAll",
            task: undefined,
          },
        ],
      ).then((entry) => {
        if (entry && entry.id === "terminateAll") {
          this._terminateAll()
        }
        const task: Task | undefined | null = entry ? entry.task : undefined
        if (task === undefined || task === null) {
          return
        }
        this.terminate(task)
      })
    }
    if (this.inTerminal()) {
      const identifier = this._getTaskIdentifier(arg)
      let promise: Promise<Task[]>
      if (identifier !== undefined) {
        promise = this.getActiveTasks()
        promise.then((tasks) => {
          for (const task of tasks) {
            if (task.matches(identifier)) {
              this.terminate(task)
              return
            }
          }
          runQuickPick(promise)
        })
      } else {
        runQuickPick()
      }
    } else {
      this._isActive().then((active) => {
        if (active) {
          this._terminateAll().then((responses) => {
            // the output runner has only one task
            const response = responses[0]
            if (response.success) {
              return
            }
            if (
              response.code &&
              response.code === TerminateResponseCode.ProcessNotFound
            ) {
              this._notificationService.error(
                nls.localize(
                  "TerminateAction.noProcess",
                  "The launched process doesn't exist anymore. If the task spawned background tasks exiting VS Code might result in orphaned processes.",
                ),
              )
            } else {
              this._notificationService.error(
                nls.localize(
                  "TerminateAction.failed",
                  "Failed to terminate running task",
                ),
              )
            }
          })
        }
      })
    }
  }

  private async _runRestartTaskCommand(arg?: any): Promise<void> {
    const activeTasks = await this.getActiveTasks()

    if (activeTasks.length === 1) {
      this._restart(activeTasks[0])
      return
    }

    if (this.inTerminal()) {
      // try dispatching using task identifier
      const identifier = this._getTaskIdentifier(arg)
      if (identifier !== undefined) {
        for (const task of activeTasks) {
          if (task.matches(identifier)) {
            this._restart(task)
            return
          }
        }
      }
      // show quick pick with active tasks
      const entry = await this._showQuickPick(
        activeTasks,
        nls.localize("TaskService.taskToRestart", "Select the task to restart"),
        {
          label: nls.localize(
            "TaskService.noTaskToRestart",
            "No task to restart",
          ),
          task: null,
        },
        false,
        true,
      )
      if (entry && entry.task) {
        this._restart(entry.task)
      }
    } else {
      if (activeTasks.length > 0) {
        this._restart(activeTasks[0])
      }
    }
  }

  private _getTaskIdentifier(
    filter?: string | ITaskIdentifier,
  ): string | KeyedTaskIdentifier | undefined {
    let result: string | KeyedTaskIdentifier | undefined = undefined
    if (Types.isString(filter)) {
      result = filter
    } else if (filter && Types.isString(filter.type)) {
      result = TaskDefinition.createTaskIdentifier(filter, console)
    }
    return result
  }

  private _configHasTasks(
    taskConfig?: TaskConfig.IExternalTaskRunnerConfiguration,
  ): boolean {
    return !!taskConfig && !!taskConfig.tasks && taskConfig.tasks.length > 0
  }

  private _openTaskFile(resource: URI, taskSource: string) {
    let configFileCreated = false
    this._fileService
      .stat(resource)
      .then(
        (stat) => stat,
        () => undefined,
      )
      .then(async (stat) => {
        const fileExists: boolean = !!stat
        const configValue =
          this._configurationService.inspect<TaskConfig.IExternalTaskRunnerConfiguration>(
            "tasks",
          )
        let tasksExistInFile: boolean
        let target: ConfigurationTarget
        switch (taskSource) {
          case TaskSourceKind.User:
            tasksExistInFile = this._configHasTasks(configValue.userValue)
            target = ConfigurationTarget.USER
            break
          case TaskSourceKind.WorkspaceFile:
            tasksExistInFile = this._configHasTasks(configValue.workspaceValue)
            target = ConfigurationTarget.WORKSPACE
            break
          default:
            tasksExistInFile = this._configHasTasks(
              configValue.workspaceFolderValue,
            )
            target = ConfigurationTarget.WORKSPACE_FOLDER
        }
        let content
        if (!tasksExistInFile) {
          const pickTemplateResult = await this._quickInputService.pick(
            getTaskTemplates(),
            {
              placeHolder: nls.localize(
                "TaskService.template",
                "Select a Task Template",
              ),
            },
          )
          if (!pickTemplateResult) {
            return Promise.resolve(undefined)
          }
          content = pickTemplateResult.content
          const editorConfig = this._configurationService.getValue() as any
          if (editorConfig.editor.insertSpaces) {
            content = content.replace(
              /(\n)(\t+)/g,
              (_, s1, s2) =>
                s1 + " ".repeat(s2.length * editorConfig.editor.tabSize),
            )
          }
          configFileCreated = true
        }

        if (!fileExists && content) {
          return this._textFileService
            .create([{ resource, value: content }])
            .then((result) => {
              return result[0].resource
            })
        } else if (fileExists && (tasksExistInFile || content)) {
          const statResource = stat?.resource
          if (content && statResource) {
            this._configurationService.updateValue(
              "tasks",
              json.parse(content),
              { resource: statResource },
              target,
            )
          }
          return statResource
        }
        return undefined
      })
      .then((resource) => {
        if (!resource) {
          return
        }
        this._editorService.openEditor({
          resource,
          options: {
            pinned: configFileCreated, // pin only if config file is created #8727
          },
        })
      })
  }

  private _isTaskEntry(
    value: IQuickPickItem,
  ): value is IQuickPickItem & { task: Task } {
    const candidate: IQuickPickItem & { task: Task } = value as any
    return candidate && !!candidate.task
  }

  private _isSettingEntry(
    value: IQuickPickItem,
  ): value is IQuickPickItem & { settingType: string } {
    const candidate: IQuickPickItem & { settingType: string } = value as any
    return candidate && !!candidate.settingType
  }

  private _configureTask(task: Task) {
    if (ContributedTask.is(task)) {
      this.customize(task, undefined, true)
    } else if (CustomTask.is(task)) {
      this.openConfig(task)
    } else if (ConfiguringTask.is(task)) {
      // Do nothing.
    }
  }

  private _handleSelection(selection: TaskQuickPickEntryType | undefined) {
    if (!selection) {
      return
    }
    if (this._isTaskEntry(selection)) {
      this._configureTask(selection.task)
    } else if (this._isSettingEntry(selection)) {
      const taskQuickPick =
        this._instantiationService.createInstance(TaskQuickPick)
      taskQuickPick.handleSettingOption(selection.settingType)
    } else if (
      selection.folder &&
      this._contextService.getWorkbenchState() !== WorkbenchState.EMPTY
    ) {
      this._openTaskFile(
        selection.folder.toResource(".vscode/tasks.json"),
        TaskSourceKind.Workspace,
      )
    } else {
      const resource = this._getResourceForKind(TaskSourceKind.User)
      if (resource) {
        this._openTaskFile(resource, TaskSourceKind.User)
      }
    }
  }

  public getTaskDescription(task: Task | ConfiguringTask): string | undefined {
    let description: string | undefined
    if (task._source.kind === TaskSourceKind.User) {
      description = nls.localize("taskQuickPick.userSettings", "User")
    } else if (task._source.kind === TaskSourceKind.WorkspaceFile) {
      description = task.getWorkspaceFileName()
    } else if (this.needsFolderQualification()) {
      const workspaceFolder = task.getWorkspaceFolder()
      if (workspaceFolder) {
        description = workspaceFolder.name
      }
    }
    return description
  }

  private async _runConfigureTasks(): Promise<void> {
    if (!(await this._trust())) {
      return
    }

    let taskPromise: Promise<TaskMap>
    if (this.schemaVersion === JsonSchemaVersion.V2_0_0) {
      taskPromise = this._getGroupedTasks()
    } else {
      taskPromise = Promise.resolve(new TaskMap())
    }

    const stats = this._contextService
      .getWorkspace()
      .folders.map<
        Promise<IFileStatWithPartialMetadata | undefined>
      >((folder) => {
        return this._fileService
          .stat(folder.toResource(".vscode/tasks.json"))
          .then(
            (stat) => stat,
            () => undefined,
          )
      })

    const createLabel = nls.localize(
      "TaskService.createJsonFile",
      "Create tasks.json file from template",
    )
    const openLabel = nls.localize(
      "TaskService.openJsonFile",
      "Open tasks.json file",
    )
    const tokenSource = new CancellationTokenSource()
    const cancellationToken: CancellationToken = tokenSource.token
    const entries = Promise.all(stats).then((stats) => {
      return taskPromise.then((taskMap) => {
        const entries: QuickPickInput<TaskQuickPickEntryType>[] = []
        let configuredCount = 0
        let tasks = taskMap.all()
        if (tasks.length > 0) {
          tasks = tasks.sort((a, b) => a._label.localeCompare(b._label))
          for (const task of tasks) {
            const entry = {
              label: TaskQuickPick.getTaskLabelWithIcon(task),
              task,
              description: this.getTaskDescription(task),
              detail: this._showDetail()
                ? task.configurationProperties.detail
                : undefined,
            }
            TaskQuickPick.applyColorStyles(task, entry, this._themeService)
            entries.push(entry)
            if (!ContributedTask.is(task)) {
              configuredCount++
            }
          }
        }
        const needsCreateOrOpen = configuredCount === 0
        // If the only configured tasks are user tasks, then we should also show the option to create from a template.
        if (
          needsCreateOrOpen ||
          taskMap.get(USER_TASKS_GROUP_KEY).length === configuredCount
        ) {
          const label = stats[0] !== undefined ? openLabel : createLabel
          if (entries.length) {
            entries.push({ type: "separator" })
          }
          entries.push({
            label,
            folder: this._contextService.getWorkspace().folders[0],
          })
        }
        if (entries.length === 1 && !needsCreateOrOpen) {
          tokenSource.cancel()
        }
        return entries
      })
    })

    const timeout: boolean = await Promise.race([
      new Promise<boolean>((resolve) => {
        entries.then(() => resolve(false))
      }),
      new Promise<boolean>((resolve) => {
        const timer = setTimeout(() => {
          clearTimeout(timer)
          resolve(true)
        }, 200)
      }),
    ])

    if (
      !timeout &&
      (await entries).length === 1 &&
      this._configurationService.getValue<boolean>(QUICKOPEN_SKIP_CONFIG)
    ) {
      const entry: any = (await entries)[0]
      if (entry.task) {
        this._handleSelection(entry)
        return
      }
    }

    const entriesWithSettings = entries.then((resolvedEntries) => {
      resolvedEntries.push(
        ...TaskQuickPick.allSettingEntries(this._configurationService),
      )
      return resolvedEntries
    })

    this._quickInputService
      .pick(
        entriesWithSettings,
        {
          placeHolder: nls.localize(
            "TaskService.pickTask",
            "Select a task to configure",
          ),
        },
        cancellationToken,
      )
      .then(async (selection) => {
        if (cancellationToken.isCancellationRequested) {
          // canceled when there's only one task
          const task = (await entries)[0]
          if ((<any>task).task) {
            selection = <TaskQuickPickEntryType>task
          }
        }
        this._handleSelection(selection)
      })
  }

  private _runConfigureDefaultBuildTask(): void {
    if (this.schemaVersion === JsonSchemaVersion.V2_0_0) {
      this.tasks().then((tasks) => {
        if (tasks.length === 0) {
          this._runConfigureTasks()
          return
        }
        const entries: QuickPickInput<TaskQuickPickEntryType>[] = []
        let selectedTask: Task | undefined
        let selectedEntry: TaskQuickPickEntryType | undefined
        this._showIgnoredFoldersMessage().then(async () => {
          const { globGroupTasks } = await this._getGlobTasks(
            TaskGroup.Build._id,
          )
          let defaultTasks = globGroupTasks
          if (!defaultTasks?.length) {
            defaultTasks = this._getDefaultTasks(tasks, false)
          }
          let defaultBuildTask
          if (defaultTasks.length === 1) {
            const group: string | TaskGroup | undefined =
              defaultTasks[0].configurationProperties.group
            if (group) {
              if (typeof group === "string" && group === TaskGroup.Build._id) {
                defaultBuildTask = defaultTasks[0]
              } else {
                defaultBuildTask = defaultTasks[0]
              }
            }
          }
          for (const task of tasks) {
            if (task === defaultBuildTask) {
              const label = nls.localize(
                "TaskService.defaultBuildTaskExists",
                "{0} is already marked as the default build task",
                TaskQuickPick.getTaskLabelWithIcon(
                  task,
                  task.getQualifiedLabel(),
                ),
              )
              selectedTask = task
              selectedEntry = {
                label,
                task,
                description: this.getTaskDescription(task),
                detail: this._showDetail()
                  ? task.configurationProperties.detail
                  : undefined,
              }
              TaskQuickPick.applyColorStyles(
                task,
                selectedEntry,
                this._themeService,
              )
            } else {
              const entry = {
                label: TaskQuickPick.getTaskLabelWithIcon(task),
                task,
                description: this.getTaskDescription(task),
                detail: this._showDetail()
                  ? task.configurationProperties.detail
                  : undefined,
              }
              TaskQuickPick.applyColorStyles(task, entry, this._themeService)
              entries.push(entry)
            }
          }
          if (selectedEntry) {
            entries.unshift(selectedEntry)
          }
          const tokenSource = new CancellationTokenSource()
          const cancellationToken: CancellationToken = tokenSource.token
          this._quickInputService
            .pick(
              entries,
              {
                placeHolder: nls.localize(
                  "TaskService.pickTask",
                  "Select a task to configure",
                ),
              },
              cancellationToken,
            )
            .then(async (entry) => {
              if (cancellationToken.isCancellationRequested) {
                // canceled when there's only one task
                const task = (await entries)[0]
                if ((<any>task).task) {
                  entry = <TaskQuickPickEntryType>task
                }
              }
              const task: Task | undefined | null =
                entry && "task" in entry ? entry.task : undefined
              if (task === undefined || task === null) {
                return
              }
              if (task === selectedTask && CustomTask.is(task)) {
                this.openConfig(task)
              }
              if (!InMemoryTask.is(task)) {
                this.customize(
                  task,
                  { group: { kind: "build", isDefault: true } },
                  true,
                ).then(() => {
                  if (
                    selectedTask &&
                    task !== selectedTask &&
                    !InMemoryTask.is(selectedTask)
                  ) {
                    this.customize(selectedTask, { group: "build" }, false)
                  }
                })
              }
            })
          this._quickInputService
            .pick(entries, {
              placeHolder: nls.localize(
                "TaskService.pickDefaultBuildTask",
                "Select the task to be used as the default build task",
              ),
            })
            .then((entry) => {
              const task: Task | undefined | null =
                entry && "task" in entry ? entry.task : undefined
              if (task === undefined || task === null) {
                return
              }
              if (task === selectedTask && CustomTask.is(task)) {
                this.openConfig(task)
              }
              if (!InMemoryTask.is(task)) {
                this.customize(
                  task,
                  { group: { kind: "build", isDefault: true } },
                  true,
                ).then(() => {
                  if (
                    selectedTask &&
                    task !== selectedTask &&
                    !InMemoryTask.is(selectedTask)
                  ) {
                    this.customize(selectedTask, { group: "build" }, false)
                  }
                })
              }
            })
        })
      })
    } else {
      this._runConfigureTasks()
    }
  }

  private _runConfigureDefaultTestTask(): void {
    if (this.schemaVersion === JsonSchemaVersion.V2_0_0) {
      this.tasks().then((tasks) => {
        if (tasks.length === 0) {
          this._runConfigureTasks()
          return
        }
        let selectedTask: Task | undefined
        let selectedEntry: ITaskQuickPickEntry

        for (const task of tasks) {
          const taskGroup: TaskGroup | undefined = TaskGroup.from(
            task.configurationProperties.group,
          )
          if (
            taskGroup &&
            taskGroup.isDefault &&
            taskGroup._id === TaskGroup.Test._id
          ) {
            selectedTask = task
            break
          }
        }
        if (selectedTask) {
          selectedEntry = {
            label: nls.localize(
              "TaskService.defaultTestTaskExists",
              "{0} is already marked as the default test task.",
              selectedTask.getQualifiedLabel(),
            ),
            task: selectedTask,
            detail: this._showDetail()
              ? selectedTask.configurationProperties.detail
              : undefined,
          }
        }

        this._showIgnoredFoldersMessage().then(() => {
          this._showQuickPick(
            tasks,
            nls.localize(
              "TaskService.pickDefaultTestTask",
              "Select the task to be used as the default test task",
            ),
            undefined,
            true,
            false,
            selectedEntry,
          ).then((entry) => {
            const task: Task | undefined | null = entry ? entry.task : undefined
            if (!task) {
              return
            }
            if (task === selectedTask && CustomTask.is(task)) {
              this.openConfig(task)
            }
            if (!InMemoryTask.is(task)) {
              this.customize(
                task,
                { group: { kind: "test", isDefault: true } },
                true,
              ).then(() => {
                if (
                  selectedTask &&
                  task !== selectedTask &&
                  !InMemoryTask.is(selectedTask)
                ) {
                  this.customize(selectedTask, { group: "test" }, false)
                }
              })
            }
          })
        })
      })
    } else {
      this._runConfigureTasks()
    }
  }

  public async runShowTasks(): Promise<void> {
    const activeTasksPromise: Promise<Task[]> = this.getActiveTasks()
    const activeTasks: Task[] = await activeTasksPromise
    let group: string | undefined
    if (activeTasks.length === 1) {
      this._taskSystem!.revealTask(activeTasks[0])
    } else if (
      activeTasks.length &&
      activeTasks.every((task) => {
        if (InMemoryTask.is(task)) {
          return false
        }

        if (!group) {
          group = task.command.presentation?.group
        }
        return (
          task.command.presentation?.group &&
          task.command.presentation.group === group
        )
      })
    ) {
      this._taskSystem!.revealTask(activeTasks[0])
    } else {
      this._showQuickPick(
        activeTasksPromise,
        nls.localize(
          "TaskService.pickShowTask",
          "Select the task to show its output",
        ),
        {
          label: nls.localize(
            "TaskService.noTaskIsRunning",
            "No task is running",
          ),
          task: null,
        },
        false,
        true,
      ).then((entry) => {
        const task: Task | undefined | null = entry ? entry.task : undefined
        if (task === undefined || task === null) {
          return
        }
        this._taskSystem!.revealTask(task)
      })
    }
  }

  private async _createTasksDotOld(
    folder: IWorkspaceFolder,
  ): Promise<[URI, URI] | undefined> {
    const tasksFile = folder.toResource(".vscode/tasks.json")
    if (await this._fileService.exists(tasksFile)) {
      const oldFile = tasksFile.with({ path: `${tasksFile.path}.old` })
      await this._fileService.copy(tasksFile, oldFile, true)
      return [oldFile, tasksFile]
    }
    return undefined
  }

  private _upgradeTask(
    task: Task,
    suppressTaskName: boolean,
    globalConfig: {
      windows?: ICommandUpgrade
      osx?: ICommandUpgrade
      linux?: ICommandUpgrade
    },
  ): TaskConfig.ICustomTask | TaskConfig.IConfiguringTask | undefined {
    if (!CustomTask.is(task)) {
      return
    }
    const configElement: any = {
      label: task._label,
    }
    const oldTaskTypes = new Set(["gulp", "jake", "grunt"])
    if (
      Types.isString(task.command.name) &&
      oldTaskTypes.has(task.command.name)
    ) {
      configElement.type = task.command.name
      configElement.task = task.command.args![0]
    } else {
      if (task.command.runtime === RuntimeType.Shell) {
        configElement.type = RuntimeType.toString(RuntimeType.Shell)
      }
      if (
        task.command.name &&
        !suppressTaskName &&
        !globalConfig.windows?.command &&
        !globalConfig.osx?.command &&
        !globalConfig.linux?.command
      ) {
        configElement.command = task.command.name
      } else if (suppressTaskName) {
        configElement.command = task._source.config.element.command
      }
      if (
        task.command.args &&
        (!Array.isArray(task.command.args) || task.command.args.length > 0)
      ) {
        if (
          !globalConfig.windows?.args &&
          !globalConfig.osx?.args &&
          !globalConfig.linux?.args
        ) {
          configElement.args = task.command.args
        } else {
          configElement.args = task._source.config.element.args
        }
      }
    }

    if (task.configurationProperties.presentation) {
      configElement.presentation = task.configurationProperties.presentation
    }
    if (task.configurationProperties.isBackground) {
      configElement.isBackground = task.configurationProperties.isBackground
    }
    if (task.configurationProperties.problemMatchers) {
      configElement.problemMatcher = task._source.config.element.problemMatcher
    }
    if (task.configurationProperties.group) {
      configElement.group = task.configurationProperties.group
    }

    task._source.config.element = configElement
    const tempTask = new CustomTask(
      task._id,
      task._source,
      task._label,
      task.type,
      task.command,
      task.hasDefinedMatchers,
      task.runOptions,
      task.configurationProperties,
    )
    const configTask = this._createCustomizableTask(tempTask)
    if (configTask) {
      return configTask
    }
    return
  }

  private async _upgrade(): Promise<void> {
    if (this.schemaVersion === JsonSchemaVersion.V2_0_0) {
      return
    }

    if (!this._workspaceTrustManagementService.isWorkspaceTrusted()) {
      this._register(
        Event.once(this._workspaceTrustManagementService.onDidChangeTrust)(
          (isTrusted) => {
            if (isTrusted) {
              this._upgrade()
            }
          },
        ),
      )
      return
    }

    const tasks = await this._getGroupedTasks()
    const fileDiffs: [URI, URI][] = []
    for (const folder of this.workspaceFolders) {
      const diff = await this._createTasksDotOld(folder)
      if (diff) {
        fileDiffs.push(diff)
      }
      if (!diff) {
        continue
      }

      const configTasks: (
        | TaskConfig.ICustomTask
        | TaskConfig.IConfiguringTask
      )[] = []
      const suppressTaskName = !!this._configurationService.getValue(
        TasksSchemaProperties.SuppressTaskName,
        { resource: folder.uri },
      )
      const globalConfig = {
        windows: <ICommandUpgrade>this._configurationService.getValue(
          TasksSchemaProperties.Windows,
          {
            resource: folder.uri,
          },
        ),
        osx: <ICommandUpgrade>this._configurationService.getValue(
          TasksSchemaProperties.Osx,
          {
            resource: folder.uri,
          },
        ),
        linux: <ICommandUpgrade>this._configurationService.getValue(
          TasksSchemaProperties.Linux,
          {
            resource: folder.uri,
          },
        ),
      }
      tasks.get(folder).forEach((task) => {
        const configTask = this._upgradeTask(
          task,
          suppressTaskName,
          globalConfig,
        )
        if (configTask) {
          configTasks.push(configTask)
        }
      })
      this._taskSystem = undefined
      this._workspaceTasksPromise = undefined
      await this._writeConfiguration(folder, "tasks.tasks", configTasks)
      await this._writeConfiguration(folder, "tasks.version", "2.0.0")
      if (
        this._configurationService.getValue(TasksSchemaProperties.ShowOutput, {
          resource: folder.uri,
        })
      ) {
        await this._configurationService.updateValue(
          TasksSchemaProperties.ShowOutput,
          undefined,
          { resource: folder.uri },
        )
      }
      if (
        this._configurationService.getValue(
          TasksSchemaProperties.IsShellCommand,
          { resource: folder.uri },
        )
      ) {
        await this._configurationService.updateValue(
          TasksSchemaProperties.IsShellCommand,
          undefined,
          { resource: folder.uri },
        )
      }
      if (
        this._configurationService.getValue(
          TasksSchemaProperties.SuppressTaskName,
          { resource: folder.uri },
        )
      ) {
        await this._configurationService.updateValue(
          TasksSchemaProperties.SuppressTaskName,
          undefined,
          { resource: folder.uri },
        )
      }
    }
    this._updateSetup()

    this._notificationService.prompt(
      Severity.Warning,
      fileDiffs.length === 1
        ? nls.localize(
            "taskService.upgradeVersion",
            "The deprecated tasks version 0.1.0 has been removed. Your tasks have been upgraded to version 2.0.0. Open the diff to review the upgrade.",
          )
        : nls.localize(
            "taskService.upgradeVersionPlural",
            "The deprecated tasks version 0.1.0 has been removed. Your tasks have been upgraded to version 2.0.0. Open the diffs to review the upgrade.",
          ),
      [
        {
          label:
            fileDiffs.length === 1
              ? nls.localize("taskService.openDiff", "Open diff")
              : nls.localize("taskService.openDiffs", "Open diffs"),
          run: async () => {
            for (const upgrade of fileDiffs) {
              await this._editorService.openEditor({
                original: { resource: upgrade[0] },
                modified: { resource: upgrade[1] },
              })
            }
          },
        },
      ],
    )
  }
}
